Skip to content

Commit

Permalink
refactor: create @pnpm/node.fetcher (#4908)
Browse files Browse the repository at this point in the history
  • Loading branch information
zkochan committed Jun 21, 2022
1 parent 91bcb44 commit ba8f23a
Show file tree
Hide file tree
Showing 16 changed files with 329 additions and 93 deletions.
5 changes: 5 additions & 0 deletions .changeset/eleven-swans-switch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pnpm/node.fetcher": minor
---

Initial release.
17 changes: 17 additions & 0 deletions packages/node.fetcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# @pnpm/node.fetcher

> Node.js artifacts fetcher
Downloads and extracts the right Node.js artifact for the active platform and architecture.

[![npm version](https://img.shields.io/npm/v/@pnpm/node.fetcher.svg)](https://www.npmjs.com/package/@pnpm/node.fetcher)

## Installation

```sh
pnpm add @pnpm/node.fetcher
```

## License

MIT
3 changes: 3 additions & 0 deletions packages/node.fetcher/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const config = require('../../jest.config.js')

module.exports = config
52 changes: 52 additions & 0 deletions packages/node.fetcher/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "@pnpm/node.fetcher",
"version": "0.0.0",
"description": "Node.js artifacts fetcher",
"funding": "https://opencollective.com/pnpm",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"files": [
"lib",
"!*.map"
],
"exports": {
".": "./lib/index.js"
},
"engines": {
"node": ">=12.17"
},
"scripts": {
"lint": "eslint src/**/*.ts test/**/*.ts",
"_test": "jest",
"test": "pnpm run compile && pnpm run _test",
"prepublishOnly": "pnpm run compile",
"compile": "tsc --build && pnpm run lint --fix"
},
"repository": "https://github.com/pnpm/pnpm/blob/main/packages/node.fetcher",
"keywords": [
"pnpm6",
"pnpm",
"env",
"node.js"
],
"license": "MIT",
"bugs": {
"url": "https://github.com/pnpm/pnpm/issues"
},
"homepage": "https://github.com/pnpm/pnpm/blob/main/packages/node.fetcher#readme",
"dependencies": {
"@pnpm/fetcher-base": "workspace:11.2.0",
"@pnpm/fetching-types": "workspace:2.2.1",
"@pnpm/package-store": "workspace:12.2.0",
"@pnpm/tarball-fetcher": "workspace:9.3.19",
"adm-zip": "^0.5.5",
"rename-overwrite": "^4.0.2",
"tempy": "^1.0.0"
},
"devDependencies": {
"@pnpm/node.fetcher": "workspace:0.0.0",
"@pnpm/prepare": "workspace:*",
"@types/adm-zip": "^0.4.34",
"node-fetch": "3.0.0-beta.9"
}
}
17 changes: 17 additions & 0 deletions packages/node.fetcher/src/getNodeTarball.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import normalizeArch from './normalizeArch'

export function getNodeTarball (
nodeVersion: string,
nodeMirror: string,
processPlatform: string,
processArch: string
) {
const platform = processPlatform === 'win32' ? 'win' : processPlatform
const arch = normalizeArch(processPlatform, processArch)
const extension = platform === 'win' ? 'zip' : 'tar.gz'
const pkgName = `node-v${nodeVersion}-${platform}-${arch}`
return {
pkgName,
tarball: `${nodeMirror}v${nodeVersion}/${pkgName}.${extension}`,
}
}
63 changes: 63 additions & 0 deletions packages/node.fetcher/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import fs from 'fs'
import path from 'path'
import {
FetchFromRegistry,
RetryTimeoutOptions,
} from '@pnpm/fetching-types'
import { FilesIndex } from '@pnpm/fetcher-base'
import { createCafsStore } from '@pnpm/package-store'
import createFetcher, { waitForFilesIndex } from '@pnpm/tarball-fetcher'
import AdmZip from 'adm-zip'
import renameOverwrite from 'rename-overwrite'
import tempy from 'tempy'
import { getNodeTarball } from './getNodeTarball'

export interface FetchNodeOptions {
cafsDir: string
fetchTimeout?: number
nodeMirrorBaseUrl: string
retry?: RetryTimeoutOptions
}

export async function fetchNode (fetch: FetchFromRegistry, version: string, targetDir: string, opts: FetchNodeOptions) {
const { tarball, pkgName } = getNodeTarball(version, opts.nodeMirrorBaseUrl, process.platform, process.arch)
if (tarball.endsWith('.zip')) {
await downloadAndUnpackZip(fetch, tarball, targetDir, pkgName)
return
}
const getCredentials = () => ({ authHeaderValue: undefined, alwaysAuth: undefined })
const { tarball: fetchTarball } = createFetcher(fetch, getCredentials, {
retry: opts.retry,
timeout: opts.fetchTimeout,
})
const cafs = createCafsStore(opts.cafsDir)
const { filesIndex } = await fetchTarball(cafs, { tarball }, {
lockfileDir: process.cwd(),
})
await cafs.importPackage(targetDir, {
filesResponse: {
filesIndex: await waitForFilesIndex(filesIndex as FilesIndex),
fromStore: false,
},
force: true,
})
}

async function downloadAndUnpackZip (
fetchFromRegistry: FetchFromRegistry,
zipUrl: string,
targetDir: string,
pkgName: string
) {
const response = await fetchFromRegistry(zipUrl)
const tmp = path.join(tempy.directory(), 'pnpm.zip')
const dest = fs.createWriteStream(tmp)
await new Promise((resolve, reject) => {
response.body!.pipe(dest).on('error', reject).on('close', resolve)
})
const zip = new AdmZip(tmp)
const nodeDir = path.dirname(targetDir)
zip.extractAllTo(nodeDir, true)
await renameOverwrite(path.join(nodeDir, pkgName), targetDir)
await fs.promises.unlink(tmp)
}
File renamed without changes.
36 changes: 36 additions & 0 deletions packages/node.fetcher/test/getNodeTarball.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { getNodeTarball } from '../lib/getNodeTarball'

test.each([
[
'16.0.0',
'https://nodejs.org/download/release/',
'win32',
'ia32',
{
pkgName: 'node-v16.0.0-win-x86',
tarball: 'https://nodejs.org/download/release/v16.0.0/node-v16.0.0-win-x86.zip',
},
],
[
'16.0.0',
'https://nodejs.org/download/release/',
'linux',
'arm',
{
pkgName: 'node-v16.0.0-linux-armv7l',
tarball: 'https://nodejs.org/download/release/v16.0.0/node-v16.0.0-linux-armv7l.tar.gz',
},
],
[
'16.0.0',
'https://nodejs.org/download/release/',
'linux',
'x64',
{
pkgName: 'node-v16.0.0-linux-x64',
tarball: 'https://nodejs.org/download/release/v16.0.0/node-v16.0.0-linux-x64.tar.gz',
},
],
])('getNodeTarball', (version, nodeMirrorBaseUrl, platform, arch, tarball) => {
expect(getNodeTarball(version, nodeMirrorBaseUrl, platform, arch)).toStrictEqual(tarball)
})
40 changes: 40 additions & 0 deletions packages/node.fetcher/test/node.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import AdmZip from 'adm-zip'
import { Response } from 'node-fetch'
import path from 'path'
import { Readable } from 'stream'
import { fetchNode, FetchNodeOptions } from '@pnpm/node.fetcher'
import { tempDir } from '@pnpm/prepare'

const fetchMock = jest.fn(async (url: string) => {
if (url.endsWith('.zip')) {
// The Windows code path for pnpm's node bootstrapping expects a subdir
// within the .zip file.
const pkgName = path.basename(url, '.zip')
const zip = new AdmZip()
zip.addFile(`${pkgName}/dummy-file`, Buffer.from('test'))

return new Response(Readable.from(zip.toBuffer()))
}

return new Response(Readable.from(Buffer.alloc(0)))
})

beforeEach(() => {
fetchMock.mockClear()
})

test('install Node uses node-mirror:release option', async () => {
tempDir()

const nodeMirrorBaseUrl = 'https://pnpm-node-mirror-test.localhost/download/release/'
const opts: FetchNodeOptions = {
nodeMirrorBaseUrl,
cafsDir: path.resolve('files'),
}

await fetchNode(fetchMock, '16.4.0', path.resolve('node'), opts)

for (const call of fetchMock.mock.calls) {
expect(call[0]).toMatch(nodeMirrorBaseUrl)
}
})
9 changes: 9 additions & 0 deletions packages/node.fetcher/test/normalizeArch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import normalizeArch from '../lib/normalizeArch'

test.each([
['win32', 'ia32', 'x86'],
['linux', 'arm', 'armv7l'], // Raspberry Pi 4
['linux', 'x64', 'x64'],
])('normalizedArch(%s, %s)', (platform, arch, normalizedArch) => {
expect(normalizeArch(platform, arch)).toBe(normalizedArch)
})
28 changes: 28 additions & 0 deletions packages/node.fetcher/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"extends": "@pnpm/tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": [
"src/**/*.ts",
"../../typings/**/*.d.ts"
],
"references": [
{
"path": "../../privatePackages/prepare"
},
{
"path": "../fetcher-base"
},
{
"path": "../fetching-types"
},
{
"path": "../package-store"
},
{
"path": "../tarball-fetcher"
}
]
}
8 changes: 8 additions & 0 deletions packages/node.fetcher/tsconfig.lint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": [
"src/**/*.ts",
"test/**/*.ts",
"../../typings/**/*.d.ts"
]
}
7 changes: 2 additions & 5 deletions packages/plugin-commands-env/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,12 @@
"@pnpm/error": "workspace:2.1.0",
"@pnpm/fetch": "workspace:4.2.7",
"@pnpm/fetcher-base": "workspace:11.2.0",
"@pnpm/package-store": "workspace:12.2.0",
"@pnpm/node.fetcher": "workspace:0.0.0",
"@pnpm/store-path": "^5.0.0",
"@pnpm/tarball-fetcher": "workspace:9.3.19",
"@zkochan/cmd-shim": "^5.2.2",
"adm-zip": "^0.5.9",
"load-json-file": "^6.2.0",
"rename-overwrite": "^4.0.2",
"render-help": "^1.0.2",
"semver": "^7.3.7",
"tempy": "^1.0.1",
"version-selector-type": "^3.0.0",
"write-json-file": "^4.3.0"
},
Expand All @@ -54,6 +50,7 @@
"@pnpm/prepare": "workspace:*",
"@types/adm-zip": "^0.4.34",
"@types/semver": "^7.3.9",
"adm-zip": "^0.5.5",
"execa": "npm:safe-execa@^0.1.1",
"nock": "12.0.3",
"node-fetch": "3.0.0-beta.9",
Expand Down
Loading

0 comments on commit ba8f23a

Please sign in to comment.