mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-23 23:29:17 -05:00
345 lines
10 KiB
TypeScript
345 lines
10 KiB
TypeScript
/// <reference path="../../../__typings__/index.d.ts"/>
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
import v8 from 'v8'
|
|
import getPort from 'get-port'
|
|
import { createClient } from '@pnpm/client'
|
|
import { readV8FileStrictSync } from '@pnpm/fs.v8-file'
|
|
import { type PackageFilesIndex } from '@pnpm/store.cafs'
|
|
import { createPackageStore } from '@pnpm/package-store'
|
|
import { connectStoreController, createServer } from '@pnpm/server'
|
|
import { type Registries } from '@pnpm/types'
|
|
import fetch from 'node-fetch'
|
|
import { sync as rimraf } from '@zkochan/rimraf'
|
|
import { temporaryDirectory } from 'tempy'
|
|
import isPortReachable from 'is-port-reachable'
|
|
|
|
const registry = 'https://registry.npmjs.org/'
|
|
|
|
const registries: Registries = { default: registry }
|
|
|
|
async function createStoreController (storeDir?: string) {
|
|
const tmp = temporaryDirectory()
|
|
if (!storeDir) {
|
|
storeDir = path.join(tmp, 'store')
|
|
}
|
|
const authConfig = { registry }
|
|
const cacheDir = path.join(tmp, 'cache')
|
|
const { resolve, fetchers, clearResolutionCache } = createClient({
|
|
authConfig,
|
|
cacheDir,
|
|
rawConfig: {},
|
|
registries,
|
|
})
|
|
return createPackageStore(resolve, fetchers, {
|
|
networkConcurrency: 1,
|
|
cacheDir,
|
|
storeDir,
|
|
verifyStoreIntegrity: true,
|
|
virtualStoreDirMaxLength: 120,
|
|
clearResolutionCache,
|
|
})
|
|
}
|
|
|
|
test('server', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeCtrlForServer = await createStoreController()
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
const storeCtrl = await connectStoreController({ remotePrefix, concurrency: 100 })
|
|
const projectDir = process.cwd()
|
|
const response = await storeCtrl.requestPackage(
|
|
{ alias: 'is-positive', bareSpecifier: '1.0.0' },
|
|
{
|
|
downloadPriority: 0,
|
|
lockfileDir: projectDir,
|
|
preferredVersions: {},
|
|
projectDir,
|
|
sideEffectsCache: false,
|
|
}
|
|
)
|
|
|
|
const { bundledManifest, files } = await response.fetching!()
|
|
expect(bundledManifest?.name).toBe('is-positive')
|
|
expect(response.body.id).toBe('is-positive@1.0.0')
|
|
|
|
expect(response.body.manifest!.name).toBe('is-positive')
|
|
expect(response.body.manifest!.version).toBe('1.0.0')
|
|
|
|
expect(files.resolvedFrom).toBe('remote')
|
|
expect(files.filesIndex.has('package.json')).toBeTruthy()
|
|
|
|
await server.close()
|
|
await storeCtrl.close()
|
|
})
|
|
|
|
test('fetchPackage', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeDir = temporaryDirectory()
|
|
const storeCtrlForServer = await createStoreController(storeDir)
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
const storeCtrl = await connectStoreController({ remotePrefix, concurrency: 100 })
|
|
const pkgId = 'registry.npmjs.org/is-positive/1.0.0'
|
|
// This should be fixed
|
|
|
|
const response = await storeCtrl.fetchPackage({
|
|
fetchRawManifest: true,
|
|
force: false,
|
|
lockfileDir: process.cwd(),
|
|
pkg: {
|
|
id: pkgId,
|
|
resolution: {
|
|
integrity: 'sha512-xxzPGZ4P2uN6rROUa5N9Z7zTX6ERuE0hs6GUOc/cKBLF2NqKc16UwqHMt3tFg4CO6EBTE5UecUasg+3jZx3Ckg==',
|
|
tarball: 'https://registry.npmjs.org/is-positive/-/is-positive-1.0.0.tgz',
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(typeof response.filesIndexFile).toBe('string')
|
|
|
|
const { bundledManifest, files } = await response.fetching!()
|
|
expect(bundledManifest).toBeTruthy()
|
|
|
|
expect(files.resolvedFrom).toBe('remote')
|
|
expect(files.filesIndex.has('package.json')).toBeTruthy()
|
|
|
|
await server.close()
|
|
await storeCtrl.close()
|
|
})
|
|
|
|
test('server errors should arrive to the client', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeCtrlForServer = await createStoreController()
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
const storeCtrl = await connectStoreController({ remotePrefix, concurrency: 100 })
|
|
let caught = false
|
|
try {
|
|
const projectDir = process.cwd()
|
|
await storeCtrl.requestPackage(
|
|
{ alias: 'not-an-existing-package', bareSpecifier: '1.0.0' },
|
|
{
|
|
downloadPriority: 0,
|
|
lockfileDir: projectDir,
|
|
preferredVersions: {},
|
|
projectDir,
|
|
sideEffectsCache: false,
|
|
}
|
|
)
|
|
} catch (e: any) { // eslint-disable-line
|
|
caught = true
|
|
expect(e.message).toBe('GET https://registry.npmjs.org/not-an-existing-package: Not Found - 404')
|
|
expect(e.hint).toBe(`not-an-existing-package is not in the npm registry, or you have no permission to fetch it.
|
|
|
|
No authorization header was set for the request.`)
|
|
expect(e.code).toBe('ERR_PNPM_FETCH_404')
|
|
expect(e.response).toBeTruthy()
|
|
expect(e.pkgName).toBeTruthy()
|
|
}
|
|
expect(caught).toBeTruthy()
|
|
|
|
await server.close()
|
|
await storeCtrl.close()
|
|
})
|
|
|
|
test('server upload', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeDir = temporaryDirectory()
|
|
const storeCtrlForServer = await createStoreController(storeDir)
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
const storeCtrl = await connectStoreController({ remotePrefix, concurrency: 100 })
|
|
|
|
const fakeEngine = 'client-engine'
|
|
const filesIndexFile = path.join(storeDir, 'fake-pkg@1.0.0.json')
|
|
|
|
fs.writeFileSync(filesIndexFile, v8.serialize({
|
|
name: 'fake-pkg',
|
|
version: '1.0.0',
|
|
files: new Map(),
|
|
}))
|
|
|
|
await storeCtrl.upload(path.join(import.meta.dirname, '__fixtures__/side-effect-fake-dir'), {
|
|
sideEffectsCacheKey: fakeEngine,
|
|
filesIndexFile,
|
|
})
|
|
|
|
const cacheIntegrity = readV8FileStrictSync<PackageFilesIndex>(filesIndexFile)
|
|
expect(Array.from(cacheIntegrity.sideEffects!.get(fakeEngine)!.added!.keys()).sort()).toStrictEqual(['side-effect.js', 'side-effect.txt'])
|
|
|
|
await server.close()
|
|
await storeCtrl.close()
|
|
})
|
|
|
|
test('disable server upload', async () => {
|
|
rimraf('.store')
|
|
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeCtrlForServer = await createStoreController()
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
ignoreUploadRequests: true,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
const storeCtrl = await connectStoreController({ remotePrefix, concurrency: 100 })
|
|
|
|
const fakeEngine = 'client-engine'
|
|
const storeDir = temporaryDirectory()
|
|
const filesIndexFile = path.join(storeDir, 'test.example.com/fake-pkg/1.0.0.json')
|
|
|
|
let thrown = false
|
|
try {
|
|
await storeCtrl.upload(path.join(import.meta.dirname, '__fixtures__/side-effect-fake-dir'), {
|
|
sideEffectsCacheKey: fakeEngine,
|
|
filesIndexFile,
|
|
})
|
|
} catch {
|
|
thrown = true
|
|
}
|
|
expect(thrown).toBeTruthy()
|
|
|
|
expect(fs.existsSync(filesIndexFile)).toBeFalsy()
|
|
|
|
await server.close()
|
|
await storeCtrl.close()
|
|
})
|
|
|
|
test('stop server with remote call', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeCtrlForServer = await createStoreController()
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
ignoreStopRequests: false,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
|
|
expect(await isPortReachable(port, { host: 'localhost' })).toBeTruthy()
|
|
|
|
const response = await fetch(`${remotePrefix}/stop`, { method: 'POST' })
|
|
|
|
expect(response.status).toBe(200)
|
|
|
|
expect(await isPortReachable(port, { host: 'localhost' })).toBeFalsy()
|
|
})
|
|
|
|
test('disallow stop server with remote call', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeCtrlForServer = await createStoreController()
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
ignoreStopRequests: true,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
|
|
expect(await isPortReachable(port, { host: 'localhost' })).toBeTruthy()
|
|
|
|
const response = await fetch(`${remotePrefix}/stop`, { method: 'POST' })
|
|
expect(response.status).toBe(403)
|
|
|
|
expect(await isPortReachable(port, { host: 'localhost' })).toBeTruthy()
|
|
|
|
await server.close()
|
|
})
|
|
|
|
test('disallow store prune', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeCtrlForServer = await createStoreController()
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
|
|
expect(await isPortReachable(port, { host: 'localhost' })).toBeTruthy()
|
|
|
|
const response = await fetch(`${remotePrefix}/prune`, { method: 'POST' })
|
|
expect(response.status).toBe(403)
|
|
|
|
await server.close()
|
|
await storeCtrlForServer.close()
|
|
})
|
|
|
|
test('server should only allow POST', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeCtrlForServer = await createStoreController()
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
|
|
expect(await isPortReachable(port, { host: 'localhost' })).toBeTruthy()
|
|
|
|
// Try various methods (not including POST)
|
|
const methods = ['GET', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
|
|
|
|
/* eslint-disable no-await-in-loop */
|
|
for (const method of methods) {
|
|
// Ensure 405 error is received
|
|
const response = await fetch(`${remotePrefix}/a-random-endpoint`, { method })
|
|
expect(response.status).toBe(405)
|
|
expect((await response.json() as any).error).toBeTruthy() // eslint-disable-line
|
|
}
|
|
/* eslint-enable no-await-in-loop */
|
|
|
|
await server.close()
|
|
await storeCtrlForServer.close()
|
|
})
|
|
|
|
test('server route not found', async () => {
|
|
const port = await getPort()
|
|
const hostname = 'localhost'
|
|
const remotePrefix = `http://${hostname}:${port}`
|
|
const storeCtrlForServer = await createStoreController()
|
|
const server = createServer(storeCtrlForServer, {
|
|
hostname,
|
|
port,
|
|
})
|
|
await server.waitForListen
|
|
|
|
expect(await isPortReachable(port, { host: 'localhost' })).toBeTruthy()
|
|
|
|
// Ensure 404 error is received
|
|
const response = await fetch(`${remotePrefix}/a-random-endpoint`, { method: 'POST' })
|
|
// Ensure error is correct
|
|
expect(response.status).toBe(404)
|
|
expect((v8.deserialize(Buffer.from(await response.arrayBuffer())) as any).error).toBeTruthy() // eslint-disable-line
|
|
|
|
await server.close()
|
|
await storeCtrlForServer.close()
|
|
})
|