mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 23:58:07 -05:00
feat: support installing Bun runtime (#9815)
* feat: support installing Bun runtime * feat: support installing Bun runtime * fix: cache libc resolution * refactor: shasum file fetching * docs: add changesets * feat: installing the right artifact * test: supported architectures * test: fix on Windows
This commit is contained in:
11
.changeset/empty-words-rescue.md
Normal file
11
.changeset/empty-words-rescue.md
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
"@pnpm/resolving.bun-resolver": major
|
||||
"@pnpm/read-project-manifest": minor
|
||||
"@pnpm/default-resolver": minor
|
||||
"@pnpm/resolver-base": minor
|
||||
"@pnpm/link-bins": minor
|
||||
"@pnpm/constants": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
Added support for installing Bun runtime.
|
||||
5
.changeset/ready-bats-sit.md
Normal file
5
.changeset/ready-bats-sit.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/crypto.shasums-file": major
|
||||
---
|
||||
|
||||
fetchShasumsFile returns an array of shasum file items.
|
||||
@@ -1,29 +1,43 @@
|
||||
import { createHash } from '@pnpm/crypto.hash'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import {
|
||||
type FetchFromRegistry,
|
||||
} from '@pnpm/fetching-types'
|
||||
|
||||
export interface ShasumsFileItem {
|
||||
integrity: string
|
||||
fileName: string
|
||||
}
|
||||
|
||||
export async function fetchShasumsFile (
|
||||
fetch: FetchFromRegistry,
|
||||
shasumsUrl: string,
|
||||
expectedVersionIntegrity?: string
|
||||
shasumsUrl: string
|
||||
): Promise<ShasumsFileItem[]> {
|
||||
const shasumsFileContent = await fetchShasumsFileRaw(fetch, shasumsUrl)
|
||||
const lines = shasumsFileContent.split('\n')
|
||||
const items: ShasumsFileItem[] = []
|
||||
for (const line of lines) {
|
||||
if (!line) continue
|
||||
const [sha256, fileName] = line.trim().split(/\s+/)
|
||||
items.push({
|
||||
integrity: `sha256-${Buffer.from(sha256, 'hex').toString('base64')}`,
|
||||
fileName,
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
export async function fetchShasumsFileRaw (
|
||||
fetch: FetchFromRegistry,
|
||||
shasumsUrl: string
|
||||
): Promise<string> {
|
||||
const res = await fetch(shasumsUrl)
|
||||
if (!res.ok) {
|
||||
throw new PnpmError(
|
||||
'NODE_FETCH_INTEGRITY_FAILED',
|
||||
'FAILED_DOWNLOAD_SHASUM_FILE',
|
||||
`Failed to fetch integrity file: ${shasumsUrl} (status: ${res.status})`
|
||||
)
|
||||
}
|
||||
|
||||
const body = await res.text()
|
||||
if (expectedVersionIntegrity) {
|
||||
const actualVersionIntegrity = createHash(body)
|
||||
if (expectedVersionIntegrity !== actualVersionIntegrity) {
|
||||
throw new PnpmError('NODE_VERSION_INTEGRITY_MISMATCH', `The integrity of ${shasumsUrl} failed. Expected: ${expectedVersionIntegrity}. Actual: ${actualVersionIntegrity}`)
|
||||
}
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
|
||||
1
deps/graph-builder/src/lockfileToDepGraph.ts
vendored
1
deps/graph-builder/src/lockfileToDepGraph.ts
vendored
@@ -234,6 +234,7 @@ async function buildGraphFromPackages (
|
||||
lockfileDir: opts.lockfileDir,
|
||||
ignoreScripts: opts.ignoreScripts,
|
||||
pkg: { name: pkgName, version: pkgVersion, id: packageId, resolution },
|
||||
supportedArchitectures: opts.supportedArchitectures,
|
||||
})
|
||||
} catch (err) {
|
||||
if (pkgSnapshot.optional) return
|
||||
|
||||
11
env/node.fetcher/src/index.ts
vendored
11
env/node.fetcher/src/index.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import path from 'path'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { fetchShasumsFile, pickFileChecksumFromShasumsFile } from '@pnpm/crypto.shasums-file'
|
||||
import { fetchShasumsFileRaw, pickFileChecksumFromShasumsFile } from '@pnpm/crypto.shasums-file'
|
||||
import {
|
||||
type FetchFromRegistry,
|
||||
type RetryTimeoutOptions,
|
||||
@@ -119,10 +119,6 @@ async function getNodeArtifactInfo (
|
||||
}
|
||||
}
|
||||
|
||||
interface LoadArtifactIntegrityOptions {
|
||||
expectedVersionIntegrity?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and extracts the integrity hash for a specific Node.js artifact.
|
||||
*
|
||||
@@ -136,10 +132,9 @@ interface LoadArtifactIntegrityOptions {
|
||||
async function loadArtifactIntegrity (
|
||||
fetch: FetchFromRegistry,
|
||||
fileName: string,
|
||||
shasumsUrl: string,
|
||||
options?: LoadArtifactIntegrityOptions
|
||||
shasumsUrl: string
|
||||
): Promise<string> {
|
||||
const body = await fetchShasumsFile(fetch, shasumsUrl, options?.expectedVersionIntegrity)
|
||||
const body = await fetchShasumsFileRaw(fetch, shasumsUrl)
|
||||
return pickFileChecksumFromShasumsFile(body, fileName)
|
||||
}
|
||||
|
||||
|
||||
13
env/node.resolver/src/index.ts
vendored
13
env/node.resolver/src/index.ts
vendored
@@ -60,21 +60,14 @@ export async function resolveNodeRuntime (
|
||||
|
||||
async function readNodeAssets (fetch: FetchFromRegistry, nodeMirrorBaseUrl: string, version: string): Promise<PlatformAssetResolution[]> {
|
||||
const integritiesFileUrl = `${nodeMirrorBaseUrl}/v${version}/SHASUMS256.txt`
|
||||
const shasumsFileContent = await fetchShasumsFile(fetch, integritiesFileUrl)
|
||||
const lines = shasumsFileContent.split('\n')
|
||||
const shasumsFileItems = await fetchShasumsFile(fetch, integritiesFileUrl)
|
||||
const escaped = version.replace(/\\/g, '\\\\').replace(/\./g, '\\.')
|
||||
const pattern = new RegExp(`^node-v${escaped}-([^-.]+)-([^.]+)\\.(?:tar\\.gz|zip)$`)
|
||||
const assets: PlatformAssetResolution[] = []
|
||||
for (const line of lines) {
|
||||
if (!line) continue
|
||||
const [sha256, file] = line.trim().split(/\s+/)
|
||||
|
||||
const match = pattern.exec(file)
|
||||
for (const { integrity, fileName } of shasumsFileItems) {
|
||||
const match = pattern.exec(fileName)
|
||||
if (!match) continue
|
||||
|
||||
const buffer = Buffer.from(sha256, 'hex')
|
||||
const base64 = buffer.toString('base64')
|
||||
const integrity = `sha256-${base64}`
|
||||
let [, platform, arch] = match
|
||||
if (platform === 'win') {
|
||||
platform = 'win32'
|
||||
|
||||
@@ -26,3 +26,7 @@ export function getNodeBinLocationForCurrentOS (platform: string = process.platf
|
||||
export function getDenoBinLocationForCurrentOS (platform: string = process.platform): string {
|
||||
return platform === 'win32' ? 'deno.exe' : 'deno'
|
||||
}
|
||||
|
||||
export function getBunBinLocationForCurrentOS (platform: string = process.platform): string {
|
||||
return platform === 'win32' ? 'bun.exe' : 'bun'
|
||||
}
|
||||
|
||||
272
pkg-manager/core/test/install/bunRuntime.ts
Normal file
272
pkg-manager/core/test/install/bunRuntime.ts
Normal file
@@ -0,0 +1,272 @@
|
||||
import { LOCKFILE_VERSION, WANTED_LOCKFILE } from '@pnpm/constants'
|
||||
import { prepareEmpty } from '@pnpm/prepare'
|
||||
import { addDependenciesToPackage, install } from '@pnpm/core'
|
||||
import { getIntegrity } from '@pnpm/registry-mock'
|
||||
import { sync as rimraf } from '@zkochan/rimraf'
|
||||
import { sync as writeYamlFile } from 'write-yaml-file'
|
||||
import { testDefaults } from '../utils'
|
||||
|
||||
const RESOLUTIONS = [
|
||||
{
|
||||
targets: [
|
||||
{
|
||||
os: 'darwin',
|
||||
cpu: 'arm64',
|
||||
},
|
||||
],
|
||||
resolution: {
|
||||
type: 'binary',
|
||||
archive: 'zip',
|
||||
url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.2.19/bun-darwin-aarch64.zip',
|
||||
integrity: 'sha256-Z0pIN4NC76rcPCkVlrVzAQ88I4iVj3xEZ42H9vt1mZE=',
|
||||
prefix: 'bun-darwin-aarch64',
|
||||
bin: 'bun',
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [
|
||||
{
|
||||
os: 'darwin',
|
||||
cpu: 'x64',
|
||||
},
|
||||
],
|
||||
resolution: {
|
||||
type: 'binary',
|
||||
archive: 'zip',
|
||||
url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.2.19/bun-darwin-x64.zip',
|
||||
integrity: 'sha256-39fkxHMRtdvTgjCzz9NX9dC+ro75eZYsW0EAj8QcJaA=',
|
||||
prefix: 'bun-darwin-x64',
|
||||
bin: 'bun',
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [
|
||||
{
|
||||
os: 'linux',
|
||||
cpu: 'arm64',
|
||||
libc: 'musl',
|
||||
},
|
||||
],
|
||||
resolution: {
|
||||
type: 'binary',
|
||||
archive: 'zip',
|
||||
url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.2.19/bun-linux-aarch64-musl.zip',
|
||||
integrity: 'sha256-ECBLT4ZeQCUI1pVr75O+Y11qek3cl0lCGxY2qseZZbY=',
|
||||
prefix: 'bun-linux-aarch64-musl',
|
||||
bin: 'bun',
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [
|
||||
{
|
||||
os: 'linux',
|
||||
cpu: 'arm64',
|
||||
},
|
||||
],
|
||||
resolution: {
|
||||
type: 'binary',
|
||||
archive: 'zip',
|
||||
url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.2.19/bun-linux-aarch64.zip',
|
||||
integrity: 'sha256-/P1HHNvVp4/Uo5DinMzSu3AEpJ01K6A3rzth1P1dC4M=',
|
||||
prefix: 'bun-linux-aarch64',
|
||||
bin: 'bun',
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [
|
||||
{
|
||||
os: 'linux',
|
||||
cpu: 'x64',
|
||||
libc: 'musl',
|
||||
},
|
||||
],
|
||||
resolution: {
|
||||
type: 'binary',
|
||||
archive: 'zip',
|
||||
url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.2.19/bun-linux-x64-musl.zip',
|
||||
integrity: 'sha256-3M13Zi0KtkLSgO704yFtYCru4VGfdTXKHYOsqRjo/os=',
|
||||
prefix: 'bun-linux-x64-musl',
|
||||
bin: 'bun',
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [
|
||||
{
|
||||
os: 'linux',
|
||||
cpu: 'x64',
|
||||
},
|
||||
],
|
||||
resolution: {
|
||||
type: 'binary',
|
||||
archive: 'zip',
|
||||
url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.2.19/bun-linux-x64.zip',
|
||||
integrity: 'sha256-w9PBTppeyD/2fQrP525DFa0G2p809Z/HsTgTeCyvH2Y=',
|
||||
prefix: 'bun-linux-x64',
|
||||
bin: 'bun',
|
||||
},
|
||||
},
|
||||
{
|
||||
targets: [
|
||||
{
|
||||
os: 'win32',
|
||||
cpu: 'x64',
|
||||
},
|
||||
],
|
||||
resolution: {
|
||||
type: 'binary',
|
||||
archive: 'zip',
|
||||
url: 'https://github.com/oven-sh/bun/releases/download/bun-v1.2.19/bun-windows-x64.zip',
|
||||
integrity: 'sha256-pIj0ZM5nsw4Ayw6lay9i5JuBw/zqe6kkYdNiJLBvdfg=',
|
||||
prefix: 'bun-windows-x64',
|
||||
bin: 'bun.exe',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
test('installing Bun runtime', async () => {
|
||||
const project = prepareEmpty()
|
||||
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['bun@runtime:1.2.19'], testDefaults({ fastUnpack: false }))
|
||||
|
||||
project.isExecutable('.bin/bun')
|
||||
expect(project.readLockfile()).toStrictEqual({
|
||||
settings: {
|
||||
autoInstallPeers: true,
|
||||
excludeLinksFromLockfile: false,
|
||||
},
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
bun: {
|
||||
specifier: 'runtime:1.2.19',
|
||||
version: 'runtime:1.2.19',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
lockfileVersion: LOCKFILE_VERSION,
|
||||
packages: {
|
||||
'bun@runtime:1.2.19': {
|
||||
hasBin: true,
|
||||
resolution: {
|
||||
type: 'variations',
|
||||
variants: RESOLUTIONS,
|
||||
},
|
||||
version: '1.2.19',
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'bun@runtime:1.2.19': {},
|
||||
},
|
||||
})
|
||||
|
||||
rimraf('node_modules')
|
||||
await install(manifest, testDefaults({ frozenLockfile: true }, {
|
||||
offline: true, // We want to verify that Bun is resolved from cache.
|
||||
}))
|
||||
project.isExecutable('.bin/bun')
|
||||
|
||||
await addDependenciesToPackage(manifest, ['@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0'], testDefaults({ fastUnpack: false }))
|
||||
project.has('@pnpm.e2e/dep-of-pkg-with-1-dep')
|
||||
|
||||
expect(project.readLockfile()).toStrictEqual({
|
||||
settings: {
|
||||
autoInstallPeers: true,
|
||||
excludeLinksFromLockfile: false,
|
||||
},
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
bun: {
|
||||
specifier: 'runtime:1.2.19',
|
||||
version: 'runtime:1.2.19',
|
||||
},
|
||||
'@pnpm.e2e/dep-of-pkg-with-1-dep': {
|
||||
specifier: '100.1.0',
|
||||
version: '100.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
lockfileVersion: LOCKFILE_VERSION,
|
||||
packages: {
|
||||
'bun@runtime:1.2.19': {
|
||||
hasBin: true,
|
||||
resolution: {
|
||||
type: 'variations',
|
||||
variants: RESOLUTIONS,
|
||||
},
|
||||
version: '1.2.19',
|
||||
},
|
||||
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0': {
|
||||
resolution: {
|
||||
integrity: getIntegrity('@pnpm.e2e/dep-of-pkg-with-1-dep', '100.1.0'),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'bun@runtime:1.2.19': {},
|
||||
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0': {},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('installing Bun runtime fails if offline mode is used and Bun not found locally', async () => {
|
||||
prepareEmpty()
|
||||
await expect(
|
||||
addDependenciesToPackage({}, ['bun@runtime:1.2.19'], testDefaults({ fastUnpack: false }, { offline: true }))
|
||||
).rejects.toThrow(/Failed to resolve bun@1.2.19 in package mirror/)
|
||||
})
|
||||
|
||||
test('installing Bun runtime fails if integrity check fails', async () => {
|
||||
prepareEmpty()
|
||||
|
||||
writeYamlFile(WANTED_LOCKFILE, {
|
||||
settings: {
|
||||
autoInstallPeers: true,
|
||||
excludeLinksFromLockfile: false,
|
||||
},
|
||||
importers: {
|
||||
'.': {
|
||||
devDependencies: {
|
||||
bun: {
|
||||
specifier: 'runtime:1.2.19',
|
||||
version: 'runtime:1.2.19',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
lockfileVersion: LOCKFILE_VERSION,
|
||||
packages: {
|
||||
'bun@runtime:1.2.19': {
|
||||
hasBin: true,
|
||||
resolution: {
|
||||
type: 'variations',
|
||||
variants: RESOLUTIONS.map((resolutionVariant) => ({
|
||||
...resolutionVariant,
|
||||
resolution: {
|
||||
...resolutionVariant.resolution,
|
||||
integrity: 'sha256-0000000000000000000000000000000000000000000=',
|
||||
},
|
||||
})),
|
||||
},
|
||||
version: '1.2.19',
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'bun@runtime:1.2.19': {},
|
||||
},
|
||||
}, {
|
||||
lineWidth: -1,
|
||||
})
|
||||
|
||||
const manifest = {
|
||||
devDependencies: {
|
||||
bun: 'runtime:1.2.19',
|
||||
},
|
||||
}
|
||||
await expect(install(manifest, testDefaults({ frozenLockfile: true }, {
|
||||
retry: {
|
||||
retries: 0,
|
||||
},
|
||||
}))).rejects.toThrow(/Got unexpected checksum for/)
|
||||
})
|
||||
@@ -331,3 +331,25 @@ test('installing Node.js runtime fails if integrity check fails', async () => {
|
||||
},
|
||||
}))).rejects.toThrow(/Got unexpected checksum for/)
|
||||
})
|
||||
|
||||
test('installing Node.js runtime for the given supported architecture', async () => {
|
||||
const isWindows = process.platform === 'win32'
|
||||
const supportedArchitectures = {
|
||||
os: [isWindows ? 'linux' : 'win32'],
|
||||
cpu: ['x64'],
|
||||
}
|
||||
const expectedBinLocation = isWindows ? 'node/bin/node' : 'node/node.exe'
|
||||
const project = prepareEmpty()
|
||||
const { updatedManifest: manifest } = await addDependenciesToPackage(
|
||||
{},
|
||||
['node@runtime:22.0.0'],
|
||||
testDefaults({
|
||||
fastUnpack: false,
|
||||
supportedArchitectures,
|
||||
})
|
||||
)
|
||||
project.has(expectedBinLocation)
|
||||
rimraf('node_modules')
|
||||
await install(manifest, testDefaults({ frozenLockfile: true, supportedArchitectures }))
|
||||
project.has(expectedBinLocation)
|
||||
})
|
||||
|
||||
@@ -229,6 +229,7 @@ async function fetchDeps (
|
||||
lockfileDir: opts.lockfileDir,
|
||||
ignoreScripts: opts.ignoreScripts,
|
||||
pkg: pkgResolution,
|
||||
supportedArchitectures: opts.supportedArchitectures,
|
||||
}) as any // eslint-disable-line
|
||||
if (fetchResponse instanceof Promise) fetchResponse = await fetchResponse
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { promises as fs, existsSync } from 'fs'
|
||||
import Module from 'module'
|
||||
import path from 'path'
|
||||
import { getNodeBinLocationForCurrentOS, getDenoBinLocationForCurrentOS } from '@pnpm/constants'
|
||||
import { getNodeBinLocationForCurrentOS, getDenoBinLocationForCurrentOS, getBunBinLocationForCurrentOS } from '@pnpm/constants'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { logger, globalWarn } from '@pnpm/logger'
|
||||
import { getAllDependenciesFromManifest } from '@pnpm/manifest-utils'
|
||||
@@ -227,6 +227,15 @@ async function getPackageBins (
|
||||
pkgVersion: '',
|
||||
makePowerShellShim: false,
|
||||
}]
|
||||
case 'bun':
|
||||
return [{
|
||||
name: 'bun',
|
||||
path: path.join(target, getBunBinLocationForCurrentOS()),
|
||||
ownName: true,
|
||||
pkgName: '',
|
||||
pkgVersion: '',
|
||||
makePowerShellShim: false,
|
||||
}]
|
||||
}
|
||||
// There's a directory in node_modules without package.json: ${target}.
|
||||
// This used to be a warning but it didn't really cause any issues.
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"@pnpm/store-controller-types": "workspace:*",
|
||||
"@pnpm/store.cafs": "workspace:*",
|
||||
"@pnpm/types": "workspace:*",
|
||||
"detect-libc": "catalog:",
|
||||
"p-defer": "catalog:",
|
||||
"p-limit": "catalog:",
|
||||
"p-queue": "catalog:",
|
||||
|
||||
@@ -42,9 +42,10 @@ import {
|
||||
type RequestPackageOptions,
|
||||
type WantedDependency,
|
||||
} from '@pnpm/store-controller-types'
|
||||
import { type DependencyManifest } from '@pnpm/types'
|
||||
import { type DependencyManifest, type SupportedArchitectures } from '@pnpm/types'
|
||||
import { depPathToFilename } from '@pnpm/dependency-path'
|
||||
import { readPkgFromCafs as _readPkgFromCafs } from '@pnpm/worker'
|
||||
import { familySync } from 'detect-libc'
|
||||
import PQueue from 'p-queue'
|
||||
import pDefer from 'p-defer'
|
||||
import pShare from 'promise-share'
|
||||
@@ -53,6 +54,13 @@ import semver from 'semver'
|
||||
import ssri from 'ssri'
|
||||
import { equalOrSemverEqual } from './equalOrSemverEqual'
|
||||
|
||||
let currentLibc: 'glibc' | 'musl' | undefined | null
|
||||
function getLibcFamilySync () {
|
||||
if (currentLibc === undefined) {
|
||||
currentLibc = familySync() as unknown as typeof currentLibc
|
||||
}
|
||||
return currentLibc
|
||||
}
|
||||
const TARBALL_INTEGRITY_FILENAME = 'tarball-integrity'
|
||||
const packageRequestLogger = logger('package-requester')
|
||||
|
||||
@@ -303,6 +311,7 @@ async function resolveAndFetch (
|
||||
resolution,
|
||||
},
|
||||
onFetchError: options.onFetchError,
|
||||
supportedArchitectures: options.supportedArchitectures,
|
||||
})
|
||||
|
||||
if (!manifest) {
|
||||
@@ -345,7 +354,7 @@ function getFilesIndexFilePath (
|
||||
storeDir: string
|
||||
virtualStoreDirMaxLength: number
|
||||
},
|
||||
opts: Pick<FetchPackageToStoreOptions, 'pkg' | 'ignoreScripts'>
|
||||
opts: Pick<FetchPackageToStoreOptions, 'pkg' | 'ignoreScripts' | 'supportedArchitectures'>
|
||||
): GetFilesIndexFilePathResult {
|
||||
const targetRelative = depPathToFilename(opts.pkg.id, ctx.virtualStoreDirMaxLength)
|
||||
const target = path.join(ctx.storeDir, targetRelative)
|
||||
@@ -358,7 +367,7 @@ function getFilesIndexFilePath (
|
||||
}
|
||||
let resolution!: AtomicResolution
|
||||
if (opts.pkg.resolution.type === 'variations') {
|
||||
resolution = findResolution(opts.pkg.resolution.variants)
|
||||
resolution = findResolution(opts.pkg.resolution.variants, opts.supportedArchitectures)
|
||||
if ((resolution as TarballResolution).integrity) {
|
||||
return {
|
||||
target,
|
||||
@@ -373,9 +382,17 @@ function getFilesIndexFilePath (
|
||||
return { filesIndexFile, target, resolution }
|
||||
}
|
||||
|
||||
function findResolution (resolutionVariants: PlatformAssetResolution[]): AtomicResolution {
|
||||
function findResolution (resolutionVariants: PlatformAssetResolution[], supportedArchitectures?: SupportedArchitectures): AtomicResolution {
|
||||
const platform = getOneIfNonCurrent(supportedArchitectures?.os) ?? process.platform
|
||||
const cpu = getOneIfNonCurrent(supportedArchitectures?.cpu) ?? process.arch
|
||||
const libc = getOneIfNonCurrent(supportedArchitectures?.libc) ?? getLibcFamilySync()
|
||||
const resolutionVariant = resolutionVariants
|
||||
.find((resolutionVariant) => resolutionVariant.targets.some((target) => target.os === process.platform && target.cpu === process.arch))
|
||||
.find((resolutionVariant) => resolutionVariant.targets.some(
|
||||
(target) =>
|
||||
target.os === platform &&
|
||||
target.cpu === cpu &&
|
||||
(target.libc == null || target.libc === libc)
|
||||
))
|
||||
if (!resolutionVariant) {
|
||||
const resolutionTargets = resolutionVariants.map((variant) => variant.targets)
|
||||
throw new PnpmError('NO_RESOLUTION_MATCHED', `Cannot find a resolution variant for the current platform in these resolutions: ${JSON.stringify(resolutionTargets)}`)
|
||||
@@ -383,6 +400,13 @@ function findResolution (resolutionVariants: PlatformAssetResolution[]): AtomicR
|
||||
return resolutionVariant.resolution
|
||||
}
|
||||
|
||||
function getOneIfNonCurrent (requirements: string[] | undefined): string | undefined {
|
||||
if (requirements?.length && requirements[0] !== 'current') {
|
||||
return requirements[0]
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function fetchToStore (
|
||||
ctx: {
|
||||
readPkgFromCafs: (
|
||||
|
||||
@@ -223,27 +223,17 @@ function createManifestWriter (
|
||||
}
|
||||
|
||||
function convertManifestAfterRead (manifest: ProjectManifest): ProjectManifest {
|
||||
if (manifest.devEngines?.runtime && !manifest.devDependencies?.['node']) {
|
||||
const runtimes = Array.isArray(manifest.devEngines.runtime) ? manifest.devEngines.runtime : [manifest.devEngines.runtime]
|
||||
const nodeRuntime = runtimes.find((runtime) => runtime.name === 'node')
|
||||
if (nodeRuntime && nodeRuntime.onFail === 'download') {
|
||||
if ('webcontainer' in process.versions) {
|
||||
globalWarn('Installation of Node.js versions is not supported in WebContainer')
|
||||
} else {
|
||||
manifest.devDependencies ??= {}
|
||||
manifest.devDependencies['node'] = `runtime:${nodeRuntime.version}`
|
||||
}
|
||||
}
|
||||
}
|
||||
if (manifest.devEngines?.runtime && !manifest.devDependencies?.['deno']) {
|
||||
const runtimes = Array.isArray(manifest.devEngines.runtime) ? manifest.devEngines.runtime : [manifest.devEngines.runtime]
|
||||
const denoRuntime = runtimes.find((runtime) => runtime.name === 'deno')
|
||||
if (denoRuntime && denoRuntime.onFail === 'download') {
|
||||
if ('webcontainer' in process.versions) {
|
||||
globalWarn('Installation of Deno versions is not supported in WebContainer')
|
||||
} else {
|
||||
manifest.devDependencies ??= {}
|
||||
manifest.devDependencies['deno'] = `runtime:${denoRuntime.version}`
|
||||
for (const runtimeName of ['node', 'deno', 'bun']) {
|
||||
if (manifest.devEngines?.runtime && !manifest.devDependencies?.[runtimeName]) {
|
||||
const runtimes = Array.isArray(manifest.devEngines.runtime) ? manifest.devEngines.runtime : [manifest.devEngines.runtime]
|
||||
const runtime = runtimes.find((runtime) => runtime.name === runtimeName)
|
||||
if (runtime && runtime.onFail === 'download') {
|
||||
if ('webcontainer' in process.versions) {
|
||||
globalWarn(`Installation of ${runtimeName} versions is not supported in WebContainer`)
|
||||
} else {
|
||||
manifest.devDependencies ??= {}
|
||||
manifest.devDependencies[runtimeName] = `runtime:${runtime.version}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,7 +241,7 @@ function convertManifestAfterRead (manifest: ProjectManifest): ProjectManifest {
|
||||
}
|
||||
|
||||
function convertManifestBeforeWrite (manifest: ProjectManifest): ProjectManifest {
|
||||
for (const runtimeName of ['node', 'deno']) {
|
||||
for (const runtimeName of ['node', 'deno', 'bun']) {
|
||||
const nodeDep = manifest.devDependencies?.[runtimeName]
|
||||
if (typeof nodeDep === 'string' && nodeDep.startsWith('runtime:')) {
|
||||
const version = nodeDep.replace(/^runtime:/, '')
|
||||
|
||||
61
pnpm-lock.yaml
generated
61
pnpm-lock.yaml
generated
@@ -5368,6 +5368,9 @@ importers:
|
||||
'@pnpm/worker':
|
||||
specifier: workspace:^
|
||||
version: link:../../worker
|
||||
detect-libc:
|
||||
specifier: 'catalog:'
|
||||
version: 2.0.3
|
||||
p-defer:
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.0
|
||||
@@ -6733,6 +6736,58 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.0
|
||||
|
||||
resolving/bun-resolver:
|
||||
dependencies:
|
||||
'@pnpm/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/constants
|
||||
'@pnpm/crypto.shasums-file':
|
||||
specifier: workspace:*
|
||||
version: link:../../crypto/shasums-file
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
'@pnpm/fetcher-base':
|
||||
specifier: workspace:*
|
||||
version: link:../../fetching/fetcher-base
|
||||
'@pnpm/fetching-types':
|
||||
specifier: workspace:*
|
||||
version: link:../../network/fetching-types
|
||||
'@pnpm/fetching.binary-fetcher':
|
||||
specifier: workspace:*
|
||||
version: link:../../fetching/binary-fetcher
|
||||
'@pnpm/node.fetcher':
|
||||
specifier: workspace:*
|
||||
version: link:../../env/node.fetcher
|
||||
'@pnpm/npm-resolver':
|
||||
specifier: workspace:*
|
||||
version: link:../npm-resolver
|
||||
'@pnpm/resolver-base':
|
||||
specifier: workspace:*
|
||||
version: link:../resolver-base
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
'@pnpm/util.lex-comparator':
|
||||
specifier: 'catalog:'
|
||||
version: 3.0.2
|
||||
'@pnpm/worker':
|
||||
specifier: workspace:*
|
||||
version: link:../../worker
|
||||
semver:
|
||||
specifier: 'catalog:'
|
||||
version: 7.7.1
|
||||
devDependencies:
|
||||
'@pnpm/resolving.bun-resolver':
|
||||
specifier: workspace:*
|
||||
version: 'link:'
|
||||
'@pnpm/resolving.deno-resolver':
|
||||
specifier: workspace:*
|
||||
version: link:../deno-resolver
|
||||
'@types/semver':
|
||||
specifier: 'catalog:'
|
||||
version: 7.5.3
|
||||
|
||||
resolving/default-resolver:
|
||||
dependencies:
|
||||
'@pnpm/error':
|
||||
@@ -6756,6 +6811,9 @@ importers:
|
||||
'@pnpm/resolver-base':
|
||||
specifier: workspace:*
|
||||
version: link:../resolver-base
|
||||
'@pnpm/resolving.bun-resolver':
|
||||
specifier: workspace:*
|
||||
version: link:../bun-resolver
|
||||
'@pnpm/resolving.deno-resolver':
|
||||
specifier: workspace:*
|
||||
version: link:../deno-resolver
|
||||
@@ -6775,6 +6833,9 @@ importers:
|
||||
'@pnpm/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/constants
|
||||
'@pnpm/crypto.shasums-file':
|
||||
specifier: workspace:*
|
||||
version: link:../../crypto/shasums-file
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
|
||||
17
resolving/bun-resolver/README.md
Normal file
17
resolving/bun-resolver/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# @pnpm/resolving.bun-resolver
|
||||
|
||||
> Resolves the Bun runtime
|
||||
|
||||
[](https://www.npmjs.com/package/@pnpm/resolving.bun-resolver)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @pnpm/resolving.bun-resolver
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
|
||||
60
resolving/bun-resolver/package.json
Normal file
60
resolving/bun-resolver/package.json
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "@pnpm/resolving.bun-resolver",
|
||||
"version": "1000.0.0-0",
|
||||
"description": "Resolves the Bun runtime",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
"pnpm10",
|
||||
"bun",
|
||||
"runtime"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"repository": "https://github.com/pnpm/pnpm/blob/main/resolving/bun-resolver",
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/main/resolving/bun-resolver#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"type": "commonjs",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint \"src/**/*.ts\"",
|
||||
"test": "pnpm run compile",
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"compile": "tsc --build && pnpm run lint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/constants": "workspace:*",
|
||||
"@pnpm/crypto.shasums-file": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/fetcher-base": "workspace:*",
|
||||
"@pnpm/fetching-types": "workspace:*",
|
||||
"@pnpm/fetching.binary-fetcher": "workspace:*",
|
||||
"@pnpm/node.fetcher": "workspace:*",
|
||||
"@pnpm/npm-resolver": "workspace:*",
|
||||
"@pnpm/resolver-base": "workspace:*",
|
||||
"@pnpm/types": "workspace:*",
|
||||
"@pnpm/util.lex-comparator": "catalog:",
|
||||
"@pnpm/worker": "workspace:*",
|
||||
"semver": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/resolving.bun-resolver": "workspace:*",
|
||||
"@pnpm/resolving.deno-resolver": "workspace:*",
|
||||
"@types/semver": "catalog:"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@pnpm/jest-config"
|
||||
}
|
||||
}
|
||||
97
resolving/bun-resolver/src/index.ts
Normal file
97
resolving/bun-resolver/src/index.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { getBunBinLocationForCurrentOS } from '@pnpm/constants'
|
||||
import { fetchShasumsFile } from '@pnpm/crypto.shasums-file'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { type FetchFromRegistry } from '@pnpm/fetching-types'
|
||||
import {
|
||||
type BinaryResolution,
|
||||
type PlatformAssetResolution,
|
||||
type PlatformAssetTarget,
|
||||
type ResolveResult,
|
||||
type VariationsResolution,
|
||||
type WantedDependency,
|
||||
} from '@pnpm/resolver-base'
|
||||
import { type PkgResolutionId } from '@pnpm/types'
|
||||
import { type NpmResolver } from '@pnpm/npm-resolver'
|
||||
import { lexCompare } from '@pnpm/util.lex-comparator'
|
||||
|
||||
export interface BunRuntimeResolveResult extends ResolveResult {
|
||||
resolution: VariationsResolution
|
||||
resolvedVia: 'github.com/oven-sh/bun'
|
||||
}
|
||||
|
||||
export async function resolveBunRuntime (
|
||||
ctx: {
|
||||
fetchFromRegistry: FetchFromRegistry
|
||||
rawConfig: Record<string, string>
|
||||
offline?: boolean
|
||||
resolveFromNpm: NpmResolver
|
||||
},
|
||||
wantedDependency: WantedDependency
|
||||
): Promise<BunRuntimeResolveResult | null> {
|
||||
if (wantedDependency.alias !== 'bun' || !wantedDependency.bareSpecifier?.startsWith('runtime:')) return null
|
||||
const versionSpec = wantedDependency.bareSpecifier.substring('runtime:'.length)
|
||||
// We use the npm registry for version resolution as it is easier than using the GitHub API for releases,
|
||||
// which uses pagination (e.g. https://api.github.com/repos/oven-sh/bun/releases?per_page=100).
|
||||
const npmResolution = await ctx.resolveFromNpm({ ...wantedDependency, bareSpecifier: versionSpec }, {})
|
||||
if (npmResolution == null) {
|
||||
throw new PnpmError('BUN_RESOLUTION_FAILURE', `Could not resolve Bun version specified as ${versionSpec}`)
|
||||
}
|
||||
const version = npmResolution.manifest.version
|
||||
const assets = await readBunAssets(ctx.fetchFromRegistry, version)
|
||||
assets.sort((asset1, asset2) => lexCompare((asset1.resolution as BinaryResolution).url, (asset2.resolution as BinaryResolution).url))
|
||||
|
||||
return {
|
||||
id: `bun@runtime:${version}` as PkgResolutionId,
|
||||
normalizedBareSpecifier: `runtime:${versionSpec}`,
|
||||
resolvedVia: 'github.com/oven-sh/bun',
|
||||
manifest: {
|
||||
name: 'bun',
|
||||
version,
|
||||
bin: getBunBinLocationForCurrentOS(),
|
||||
},
|
||||
resolution: {
|
||||
type: 'variations',
|
||||
variants: assets,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function readBunAssets (fetch: FetchFromRegistry, version: string): Promise<PlatformAssetResolution[]> {
|
||||
const integritiesFileUrl = `https://github.com/oven-sh/bun/releases/download/bun-v${version}/SHASUMS256.txt`
|
||||
const shasumsFileItems = await fetchShasumsFile(fetch, integritiesFileUrl)
|
||||
const pattern = /^bun-([^-.]+)-([^-.]+)(-musl)?\.zip$/
|
||||
const assets: PlatformAssetResolution[] = []
|
||||
for (const { integrity, fileName } of shasumsFileItems) {
|
||||
const match = pattern.exec(fileName)
|
||||
if (!match) continue
|
||||
|
||||
let [, platform, arch, musl] = match
|
||||
if (platform === 'windows') {
|
||||
platform = 'win32'
|
||||
}
|
||||
if (arch === 'aarch64') {
|
||||
arch = 'arm64'
|
||||
}
|
||||
const url = `https://github.com/oven-sh/bun/releases/download/bun-v${version}/${fileName}`
|
||||
const resolution: BinaryResolution = {
|
||||
type: 'binary',
|
||||
archive: 'zip',
|
||||
bin: getBunBinLocationForCurrentOS(platform),
|
||||
integrity,
|
||||
url,
|
||||
prefix: fileName.replace(/\.zip$/, ''),
|
||||
}
|
||||
const target: PlatformAssetTarget = {
|
||||
os: platform,
|
||||
cpu: arch,
|
||||
}
|
||||
if (musl != null) {
|
||||
target.libc = 'musl'
|
||||
}
|
||||
assets.push({
|
||||
targets: [target],
|
||||
resolution,
|
||||
})
|
||||
}
|
||||
return assets
|
||||
}
|
||||
49
resolving/bun-resolver/tsconfig.json
Normal file
49
resolving/bun-resolver/tsconfig.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../crypto/shasums-file"
|
||||
},
|
||||
{
|
||||
"path": "../../env/node.fetcher"
|
||||
},
|
||||
{
|
||||
"path": "../../fetching/binary-fetcher"
|
||||
},
|
||||
{
|
||||
"path": "../../fetching/fetcher-base"
|
||||
},
|
||||
{
|
||||
"path": "../../network/fetching-types"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/constants"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/types"
|
||||
},
|
||||
{
|
||||
"path": "../../worker"
|
||||
},
|
||||
{
|
||||
"path": "../deno-resolver"
|
||||
},
|
||||
{
|
||||
"path": "../npm-resolver"
|
||||
},
|
||||
{
|
||||
"path": "../resolver-base"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
resolving/bun-resolver/tsconfig.lint.json
Normal file
8
resolving/bun-resolver/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -40,6 +40,7 @@
|
||||
"@pnpm/node.resolver": "workspace:*",
|
||||
"@pnpm/npm-resolver": "workspace:*",
|
||||
"@pnpm/resolver-base": "workspace:*",
|
||||
"@pnpm/resolving.bun-resolver": "workspace:*",
|
||||
"@pnpm/resolving.deno-resolver": "workspace:*",
|
||||
"@pnpm/tarball-resolver": "workspace:*"
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import { type GitResolveResult, createGitResolver } from '@pnpm/git-resolver'
|
||||
import { type LocalResolveResult, resolveFromLocal } from '@pnpm/local-resolver'
|
||||
import { resolveNodeRuntime, type NodeRuntimeResolveResult } from '@pnpm/node.resolver'
|
||||
import { resolveDenoRuntime, type DenoRuntimeResolveResult } from '@pnpm/resolving.deno-resolver'
|
||||
import { resolveBunRuntime, type BunRuntimeResolveResult } from '@pnpm/resolving.bun-resolver'
|
||||
import {
|
||||
createNpmResolver,
|
||||
type JsrResolveResult,
|
||||
@@ -37,6 +38,7 @@ export type DefaultResolveResult =
|
||||
| WorkspaceResolveResult
|
||||
| NodeRuntimeResolveResult
|
||||
| DenoRuntimeResolveResult
|
||||
| BunRuntimeResolveResult
|
||||
|
||||
export type DefaultResolver = (wantedDependency: WantedDependency, opts: ResolveOptions) => Promise<DefaultResolveResult>
|
||||
|
||||
@@ -54,6 +56,7 @@ export function createResolver (
|
||||
})
|
||||
const _resolveNodeRuntime = resolveNodeRuntime.bind(null, { fetchFromRegistry, offline: pnpmOpts.offline, rawConfig: pnpmOpts.rawConfig })
|
||||
const _resolveDenoRuntime = resolveDenoRuntime.bind(null, { fetchFromRegistry, offline: pnpmOpts.offline, rawConfig: pnpmOpts.rawConfig, resolveFromNpm })
|
||||
const _resolveBunRuntime = resolveBunRuntime.bind(null, { fetchFromRegistry, offline: pnpmOpts.offline, rawConfig: pnpmOpts.rawConfig, resolveFromNpm })
|
||||
return {
|
||||
resolve: async (wantedDependency, opts) => {
|
||||
const resolution = await resolveFromNpm(wantedDependency, opts as ResolveFromNpmOptions) ??
|
||||
@@ -64,7 +67,8 @@ export function createResolver (
|
||||
await _resolveFromLocal(wantedDependency as { bareSpecifier: string }, opts)
|
||||
)) ??
|
||||
await _resolveNodeRuntime(wantedDependency) ??
|
||||
await _resolveDenoRuntime(wantedDependency)
|
||||
await _resolveDenoRuntime(wantedDependency) ??
|
||||
await _resolveBunRuntime(wantedDependency)
|
||||
if (!resolution) {
|
||||
throw new PnpmError(
|
||||
'SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER',
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
},
|
||||
{
|
||||
"path": "../bun-resolver"
|
||||
},
|
||||
{
|
||||
"path": "../deno-resolver"
|
||||
},
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/constants": "workspace:*",
|
||||
"@pnpm/crypto.shasums-file": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/fetcher-base": "workspace:*",
|
||||
"@pnpm/fetching-types": "workspace:*",
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../crypto/shasums-file"
|
||||
},
|
||||
{
|
||||
"path": "../../env/node.fetcher"
|
||||
},
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface GitResolution {
|
||||
export interface PlatformAssetTarget {
|
||||
os: string
|
||||
cpu: string
|
||||
libc?: 'musl'
|
||||
}
|
||||
|
||||
export interface PlatformAssetResolution {
|
||||
|
||||
@@ -93,6 +93,7 @@ export interface FetchPackageToStoreOptions {
|
||||
resolution: Resolution
|
||||
}
|
||||
onFetchError?: OnFetchError
|
||||
supportedArchitectures?: SupportedArchitectures
|
||||
}
|
||||
|
||||
export type OnFetchError = (error: Error) => Error
|
||||
|
||||
Reference in New Issue
Block a user