feat: support installing Deno runtime (#9791)

* feat: support installing Deno runtime

* refactor: use npm registry to resolve deno version

* feat: wip

* feat: installing deno runtime

* style: fix

* test: fix

* test: deno

* test: fix

* feat: deno

* feat: deno

* feat: create zip fetcher

* style: fix

* refactor: node fetch

* feat: support a new binary fetcher

* test: fix

* feat: rename zip-fetcher to binary-fetcher

* refactor: change naming

* fix: windows

* refactor: rename packages

* refactor: deno resolver

* refactor: runtime resolvers

* refactor: binary fetcher

* refactor: runtime resolvers

* refactor: runtime resolvers

* refactor: create SingleResolution

* refactor: remove not needed change

* refactor: package requester

* docs: add changesets

* refactor: use VariationsResolution and AtomicResolution

* refactor: implement CR suggestions

* docs: add changesets

* fix: address comment in CR

* feat: update formatting of pnpm-lock.yaml
This commit is contained in:
Zoltan Kochan
2025-07-30 11:27:07 +02:00
committed by GitHub
parent b0cba9fa77
commit d1edf732ad
49 changed files with 1304 additions and 526 deletions

View File

@@ -0,0 +1,10 @@
---
"@pnpm/dependency-path": minor
"@pnpm/constants": minor
"@pnpm/link-bins": minor
"@pnpm/read-project-manifest": minor
"@pnpm/default-resolver": minor
"@pnpm/resolving.deno-resolver": major
---
Add support for installing deno runtime.

View File

@@ -0,0 +1,14 @@
---
"@pnpm/node.fetcher": major
"@pnpm/node.resolver": major
"@pnpm/fetcher-base": major
"@pnpm/lockfile.types": major
"@pnpm/lockfile.utils": major
"@pnpm/client": major
"@pnpm/core": major
"@pnpm/package-requester": major
"@pnpm/resolve-dependencies": major
"@pnpm/plugin-commands-deploy": major
---
Removed node fetcher. The binary fetcher should be used for downloading node assets.

View File

@@ -0,0 +1,6 @@
---
"@pnpm/fetching.binary-fetcher": major
"@pnpm/client": minor
---
Added support for binary fetcher.

View File

@@ -0,0 +1,6 @@
---
"@pnpm/pick-fetcher": major
"@pnpm/resolver-base": major
---
Rename Resolution to AtomicResolution. Add support for binary resolution.

View File

@@ -40,6 +40,7 @@
"cves",
"cwsay",
"deburr",
"denoland",
"denolib",
"deptype",
"devextreme",
@@ -136,6 +137,7 @@
"monorepolint",
"moonrepo",
"mountpoint",
"msvc",
"msys",
"mycomp",
"mycompany",

View File

@@ -33,27 +33,21 @@
"compile": "tsc --build && pnpm run lint --fix"
},
"dependencies": {
"@pnpm/constants": "workspace:*",
"@pnpm/create-cafs-store": "workspace:*",
"@pnpm/crypto.shasums-file": "workspace:*",
"@pnpm/error": "workspace:*",
"@pnpm/fetcher-base": "workspace:*",
"@pnpm/fetching-types": "workspace:*",
"@pnpm/fetching.binary-fetcher": "workspace:*",
"@pnpm/node.resolver": "workspace:*",
"@pnpm/tarball-fetcher": "workspace:*",
"@pnpm/worker": "workspace:*",
"adm-zip": "catalog:",
"detect-libc": "catalog:",
"rename-overwrite": "catalog:",
"ssri": "catalog:",
"tempy": "catalog:"
"detect-libc": "catalog:"
},
"devDependencies": {
"@pnpm/cafs-types": "workspace:*",
"@pnpm/node.fetcher": "workspace:*",
"@pnpm/prepare": "workspace:*",
"@types/adm-zip": "catalog:",
"@types/ssri": "catalog:",
"adm-zip": "catalog:",
"node-fetch": "catalog:"
},
"engines": {

View File

@@ -1,6 +1,4 @@
import fsPromises from 'fs/promises'
import path from 'path'
import { getNodeBinLocationForCurrentOS } from '@pnpm/constants'
import { PnpmError } from '@pnpm/error'
import { fetchShasumsFile, pickFileChecksumFromShasumsFile } from '@pnpm/crypto.shasums-file'
import {
@@ -10,75 +8,9 @@ import {
import { createCafsStore } from '@pnpm/create-cafs-store'
import { type Cafs } from '@pnpm/cafs-types'
import { createTarballFetcher } from '@pnpm/tarball-fetcher'
import { type NodeRuntimeFetcher, type FetchFunction } from '@pnpm/fetcher-base'
import { getNodeMirror, parseEnvSpecifier } from '@pnpm/node.resolver'
import { addFilesFromDir } from '@pnpm/worker'
import AdmZip from 'adm-zip'
import renameOverwrite from 'rename-overwrite'
import tempy from 'tempy'
import { getNodeArtifactAddress } from '@pnpm/node.resolver'
import { downloadAndUnpackZip } from '@pnpm/fetching.binary-fetcher'
import { isNonGlibcLinux } from 'detect-libc'
import ssri from 'ssri'
import { getNodeArtifactAddress } from './getNodeArtifactAddress'
export function createNodeRuntimeFetcher (ctx: {
fetchFromRemoteTarball: FetchFunction
fetch: FetchFromRegistry
rawConfig: Record<string, string>
offline?: boolean
}): { nodeRuntime: NodeRuntimeFetcher } {
const fetchNodeRuntime: NodeRuntimeFetcher = async (cafs, resolution, opts) => {
if (!opts.pkg.version) {
throw new PnpmError('CANNOT_FETCH_NODE_WITHOUT_VERSION', 'Cannot fetch Node.js without a version')
}
if (ctx.offline) {
throw new PnpmError('CANNOT_DOWNLOAD_NODE_OFFLINE', 'Cannot download Node.js because offline mode is enabled.')
}
const version = opts.pkg.version
const { releaseChannel } = parseEnvSpecifier(version)
await validateSystemCompatibility()
const nodeMirrorBaseUrl = getNodeMirror(ctx.rawConfig, releaseChannel)
const artifactInfo = await getNodeArtifactInfo(ctx.fetch, version, {
nodeMirrorBaseUrl,
integrities: resolution.integrities,
})
const manifest = {
name: 'node',
version,
bin: getNodeBinLocationForCurrentOS(),
}
if (artifactInfo.isZip) {
const tempLocation = await cafs.tempDir()
await downloadAndUnpackZip(ctx.fetch, artifactInfo, tempLocation)
return {
...await addFilesFromDir({
storeDir: cafs.storeDir,
dir: tempLocation,
filesIndexFile: opts.filesIndexFile,
readManifest: false,
}),
manifest,
}
}
return {
...await ctx.fetchFromRemoteTarball(cafs, {
tarball: artifactInfo.url,
integrity: artifactInfo.integrity,
}, {
filesIndexFile: opts.filesIndexFile,
lockfileDir: process.cwd(),
pkg: {},
}),
manifest,
}
}
return {
nodeRuntime: fetchNodeRuntime,
}
}
// Constants
const DEFAULT_NODE_MIRROR_BASE_URL = 'https://nodejs.org/download/release/'
@@ -258,83 +190,3 @@ async function downloadAndUnpackTarballToDir (
force: true,
})
}
/**
* Downloads and unpacks a zip file containing Node.js.
*
* @param fetchFromRegistry - Function to fetch resources from registry
* @param artifactInfo - Information about the Node.js artifact
* @param targetDir - Directory where Node.js should be installed
* @throws {PnpmError} When integrity verification fails or extraction fails
*/
async function downloadAndUnpackZip (
fetchFromRegistry: FetchFromRegistry,
artifactInfo: NodeArtifactInfo,
targetDir: string
): Promise<void> {
const tmp = path.join(tempy.directory(), 'pnpm.zip')
try {
await downloadWithIntegrityCheck(fetchFromRegistry, artifactInfo, tmp)
await extractZipToTarget(tmp, artifactInfo.basename, targetDir)
} finally {
// Clean up temporary file
try {
await fsPromises.unlink(tmp)
} catch {
// Ignore cleanup errors
}
}
}
/**
* Downloads a file with integrity verification.
*/
async function downloadWithIntegrityCheck (
fetchFromRegistry: FetchFromRegistry,
{ url, integrity }: NodeArtifactInfo,
tmpPath: string
): Promise<void> {
const response = await fetchFromRegistry(url)
// Collect all chunks from the response
const chunks: Buffer[] = []
for await (const chunk of response.body!) {
chunks.push(chunk as Buffer)
}
const data = Buffer.concat(chunks)
try {
// Verify integrity if provided
ssri.checkData(data, integrity, { error: true })
} catch (err) {
if (!(err instanceof Error) || !('expected' in err) || !('found' in err)) {
throw err
}
throw new PnpmError('TARBALL_INTEGRITY', `Got unexpected checksum for "${url}". Wanted "${err.expected as string}". Got "${err.found as string}".`)
}
// Write the verified data to file
await fsPromises.writeFile(tmpPath, data)
}
/**
* Extracts a zip file to the target directory.
*
* @param zipPath - Path to the zip file
* @param basename - Base name of the file (without extension)
* @param targetDir - Directory where contents should be extracted
* @throws {PnpmError} When extraction fails
*/
async function extractZipToTarget (
zipPath: string,
basename: string,
targetDir: string
): Promise<void> {
const zip = new AdmZip(zipPath)
const nodeDir = path.dirname(targetDir)
const extractedDir = path.join(nodeDir, basename)
zip.extractAllTo(nodeDir, true)
await renameOverwrite(extractedDir, targetDir)
}

View File

@@ -16,7 +16,7 @@
"path": "../../crypto/shasums-file"
},
{
"path": "../../fetching/fetcher-base"
"path": "../../fetching/binary-fetcher"
},
{
"path": "../../fetching/tarball-fetcher"
@@ -24,9 +24,6 @@
{
"path": "../../network/fetching-types"
},
{
"path": "../../packages/constants"
},
{
"path": "../../packages/error"
},
@@ -36,9 +33,6 @@
{
"path": "../../store/create-cafs-store"
},
{
"path": "../../worker"
},
{
"path": "../node.resolver"
}

View File

@@ -2,17 +2,24 @@ import { getNodeBinLocationForCurrentOS } from '@pnpm/constants'
import { fetchShasumsFile } from '@pnpm/crypto.shasums-file'
import { PnpmError } from '@pnpm/error'
import { type FetchFromRegistry } from '@pnpm/fetching-types'
import { type WantedDependency, type NodeRuntimeResolution, type ResolveResult } from '@pnpm/resolver-base'
import {
type BinaryResolution,
type PlatformAssetResolution,
type ResolveResult,
type VariationsResolution,
type WantedDependency,
} from '@pnpm/resolver-base'
import semver from 'semver'
import versionSelectorType from 'version-selector-type'
import { type PkgResolutionId } from '@pnpm/types'
import { parseEnvSpecifier } from './parseEnvSpecifier'
import { getNodeMirror } from './getNodeMirror'
import { getNodeArtifactAddress } from './getNodeArtifactAddress'
export { getNodeMirror, parseEnvSpecifier }
export { getNodeMirror, parseEnvSpecifier, getNodeArtifactAddress }
export interface NodeRuntimeResolveResult extends ResolveResult {
resolution: NodeRuntimeResolution
resolution: VariationsResolution
resolvedVia: 'nodejs.org'
}
@@ -33,7 +40,7 @@ export async function resolveNodeRuntime (
if (!version) {
throw new PnpmError('NODEJS_VERSION_NOT_FOUND', `Could not find a Node.js version that satisfies ${versionSpec}`)
}
const integrities = await loadShasumsFile(ctx.fetchFromRegistry, nodeMirrorBaseUrl, version)
const variants = await readNodeAssets(ctx.fetchFromRegistry, nodeMirrorBaseUrl, version)
const range = version === versionSpec ? version : `^${version}`
return {
id: `node@runtime:${version}` as PkgResolutionId,
@@ -45,19 +52,19 @@ export async function resolveNodeRuntime (
bin: getNodeBinLocationForCurrentOS(),
},
resolution: {
type: 'nodeRuntime',
integrities,
type: 'variations',
variants,
},
}
}
async function loadShasumsFile (fetch: FetchFromRegistry, nodeMirrorBaseUrl: string, version: string): Promise<Record<string, string>> {
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 integrities: Record<string, string> = {}
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+/)
@@ -72,10 +79,32 @@ async function loadShasumsFile (fetch: FetchFromRegistry, nodeMirrorBaseUrl: str
if (platform === 'win') {
platform = 'win32'
}
integrities[`${platform}-${arch}`] = integrity
const address = getNodeArtifactAddress({
version,
baseUrl: nodeMirrorBaseUrl,
platform,
arch,
})
const url = `${address.dirname}/${address.basename}${address.extname}`
const resolution: BinaryResolution = {
type: 'binary',
archive: address.extname === '.zip' ? 'zip' : 'tarball',
bin: getNodeBinLocationForCurrentOS(platform),
integrity,
url,
}
if (resolution.archive === 'zip') {
resolution.prefix = address.basename
}
assets.push({
targets: [{
os: platform,
cpu: arch,
}],
resolution,
})
}
return integrities
return assets
}
interface NodeVersion {

View File

@@ -0,0 +1,13 @@
# @pnpm/fetching.binary-fetcher
> A fetcher for binary archives
## Installation
```
pnpm add @pnpm/fetching.binary-fetcher
```
## License
MIT

View File

@@ -0,0 +1,53 @@
{
"name": "@pnpm/fetching.binary-fetcher",
"version": "1000.0.0-0",
"description": "A fetcher for binary archives",
"keywords": [
"pnpm",
"pnpm10"
],
"license": "MIT",
"funding": "https://opencollective.com/pnpm",
"repository": "https://github.com/pnpm/pnpm/blob/main/fetching/binary-fetcher",
"homepage": "https://github.com/pnpm/pnpm/blob/main/fetching/binary-fetcher#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/error": "workspace:*",
"@pnpm/fetcher-base": "workspace:*",
"@pnpm/fetching-types": "workspace:*",
"@pnpm/worker": "workspace:*",
"adm-zip": "catalog:",
"rename-overwrite": "catalog:",
"ssri": "catalog:",
"tempy": "catalog:"
},
"devDependencies": {
"@pnpm/fetching.binary-fetcher": "workspace:*",
"@types/adm-zip": "catalog:",
"@types/ssri": "catalog:"
},
"engines": {
"node": ">=18.12"
},
"jest": {
"preset": "@pnpm/jest-config"
}
}

View File

@@ -0,0 +1,146 @@
import path from 'path'
import fsPromises from 'fs/promises'
import { PnpmError } from '@pnpm/error'
import { type FetchFromRegistry } from '@pnpm/fetching-types'
import { type BinaryFetcher, type FetchFunction } from '@pnpm/fetcher-base'
import { addFilesFromDir } from '@pnpm/worker'
import AdmZip from 'adm-zip'
import renameOverwrite from 'rename-overwrite'
import tempy from 'tempy'
import ssri from 'ssri'
export function createBinaryFetcher (ctx: {
fetch: FetchFromRegistry
fetchFromRemoteTarball: FetchFunction
rawConfig: Record<string, string>
offline?: boolean
}): { binary: BinaryFetcher } {
const fetchBinary: BinaryFetcher = async (cafs, resolution, opts) => {
if (ctx.offline) {
throw new PnpmError('CANNOT_DOWNLOAD_BINARY_OFFLINE', `Cannot download binary "${resolution.url}" because offline mode is enabled.`)
}
const version = opts.pkg.version!
const manifest = {
name: opts.pkg.name!,
version,
bin: resolution.bin,
}
if (resolution.archive === 'tarball') {
return {
...await ctx.fetchFromRemoteTarball(cafs, {
tarball: resolution.url,
integrity: resolution.integrity,
}, opts),
manifest,
}
}
if (resolution.archive === 'zip') {
const tempLocation = await cafs.tempDir()
await downloadAndUnpackZip(ctx.fetch, {
url: resolution.url,
integrity: resolution.integrity,
basename: resolution.prefix ?? '',
}, tempLocation)
return {
...await addFilesFromDir({
storeDir: cafs.storeDir,
dir: tempLocation,
filesIndexFile: opts.filesIndexFile,
readManifest: false,
}),
manifest,
}
}
throw new PnpmError('NOT_SUPPORTED_ARCHIVE', `The binary fetcher doesn't support archive type ${resolution.archive as string}`)
}
return {
binary: fetchBinary,
}
}
export interface AssetInfo {
url: string
integrity: string
basename: string
}
/**
* Downloads and unpacks a zip file containing a binary asset.
*
* @param fetchFromRegistry - Function to fetch resources from registry
* @param assetInfo - Information about the binary asset
* @param targetDir - Directory where the binary asset should be installed
* @throws {PnpmError} When integrity verification fails or extraction fails
*/
export async function downloadAndUnpackZip (
fetchFromRegistry: FetchFromRegistry,
assetInfo: AssetInfo,
targetDir: string
): Promise<void> {
const tmp = path.join(tempy.directory(), 'pnpm.zip')
try {
await downloadWithIntegrityCheck(fetchFromRegistry, assetInfo, tmp)
await extractZipToTarget(tmp, assetInfo.basename, targetDir)
} finally {
// Clean up temporary file
try {
await fsPromises.unlink(tmp)
} catch {
// Ignore cleanup errors
}
}
}
/**
* Downloads a file with integrity verification.
*/
async function downloadWithIntegrityCheck (
fetchFromRegistry: FetchFromRegistry,
{ url, integrity }: AssetInfo,
tmpPath: string
): Promise<void> {
const response = await fetchFromRegistry(url)
// Collect all chunks from the response
const chunks: Buffer[] = []
for await (const chunk of response.body!) {
chunks.push(chunk as Buffer)
}
const data = Buffer.concat(chunks)
try {
// Verify integrity if provided
ssri.checkData(data, integrity, { error: true })
} catch (err) {
if (!(err instanceof Error) || !('expected' in err) || !('found' in err)) {
throw err
}
throw new PnpmError('TARBALL_INTEGRITY', `Got unexpected checksum for "${url}". Wanted "${err.expected as string}". Got "${err.found as string}".`)
}
// Write the verified data to file
await fsPromises.writeFile(tmpPath, data)
}
/**
* Extracts a zip file to the target directory.
*
* @param zipPath - Path to the zip file
* @param basename - Base name of the file (without extension)
* @param targetDir - Directory where contents should be extracted
* @throws {PnpmError} When extraction fails
*/
async function extractZipToTarget (
zipPath: string,
basename: string,
targetDir: string
): Promise<void> {
const zip = new AdmZip(zipPath)
const nodeDir = basename === '' ? targetDir : path.dirname(targetDir)
const extractedDir = path.join(nodeDir, basename)
zip.extractAllTo(nodeDir, true)
await renameOverwrite(extractedDir, targetDir)
}

View File

@@ -0,0 +1,25 @@
{
"extends": "@pnpm/tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": [
"src/**/*.ts",
"../../__typings__/**/*.d.ts"
],
"references": [
{
"path": "../../network/fetching-types"
},
{
"path": "../../packages/error"
},
{
"path": "../../worker"
},
{
"path": "../fetcher-base"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": [
"src/**/*.ts",
"test/**/*.ts",
"../../__typings__/**/*.d.ts"
]
}

View File

@@ -2,7 +2,7 @@ import {
type Resolution,
type GitResolution,
type DirectoryResolution,
type NodeRuntimeResolution,
type BinaryResolution,
} from '@pnpm/resolver-base'
import { type Cafs } from '@pnpm/cafs-types'
import { type DependencyManifest } from '@pnpm/types'
@@ -48,7 +48,7 @@ export interface GitFetcherResult {
export type GitFetcher = FetchFunction<GitResolution, GitFetcherOptions, GitFetcherResult>
export type NodeRuntimeFetcher = FetchFunction<NodeRuntimeResolution>
export type BinaryFetcher = FetchFunction<BinaryResolution>
export interface DirectoryFetcherOptions {
lockfileDir: string
@@ -71,7 +71,7 @@ export interface Fetchers {
gitHostedTarball: FetchFunction
directory: DirectoryFetcher
git: GitFetcher
nodeRuntime: NodeRuntimeFetcher
binary: BinaryFetcher
}
interface CustomFetcherFactoryOptions {

View File

@@ -1,7 +1,7 @@
import type { Resolution } from '@pnpm/resolver-base'
import type { Fetchers, FetchFunction, DirectoryFetcher, GitFetcher, NodeRuntimeFetcher } from '@pnpm/fetcher-base'
import type { AtomicResolution } from '@pnpm/resolver-base'
import type { Fetchers, FetchFunction, DirectoryFetcher, GitFetcher, BinaryFetcher } from '@pnpm/fetcher-base'
export function pickFetcher (fetcherByHostingType: Partial<Fetchers>, resolution: Resolution): FetchFunction | DirectoryFetcher | GitFetcher | NodeRuntimeFetcher {
export function pickFetcher (fetcherByHostingType: Partial<Fetchers>, resolution: AtomicResolution): FetchFunction | DirectoryFetcher | GitFetcher | BinaryFetcher {
let fetcherType: keyof Fetchers | undefined = resolution.type
if (resolution.type == null) {

View File

@@ -32,6 +32,7 @@
},
"dependencies": {
"@pnpm/patching.types": "workspace:*",
"@pnpm/resolver-base": "workspace:*",
"@pnpm/types": "workspace:*"
},
"devDependencies": {

View File

@@ -1,5 +1,6 @@
import { type PatchFile } from '@pnpm/patching.types'
import { type DependenciesMeta, type DepPath, type ProjectId } from '@pnpm/types'
import { type PlatformAssetTarget } from '@pnpm/resolver-base'
export type { PatchFile, ProjectId }
@@ -109,18 +110,31 @@ export interface GitRepositoryResolution {
path?: string
}
export interface NodeRuntimeResolution {
type: 'nodeRuntime'
integrities: Record<string, string>
export interface BinaryResolution {
type: 'binary'
url: string
integrity: string
bin: string
archive: 'zip' | 'tarball'
}
export interface PlatformAssetResolution {
resolution: Resolution
targets: PlatformAssetTarget[]
}
export type Resolution =
TarballResolution |
GitRepositoryResolution |
DirectoryResolution |
NodeRuntimeResolution
BinaryResolution
export type LockfileResolution = Resolution | {
export interface VariationsResolution {
type: 'variations'
variants: PlatformAssetResolution[]
}
export type LockfileResolution = Resolution | VariationsResolution | {
integrity: string
}

View File

@@ -14,6 +14,9 @@
},
{
"path": "../../patching/types"
},
{
"path": "../../resolving/resolver-base"
}
]
}

View File

@@ -17,7 +17,7 @@ export function nameVerFromPkgSnapshot (
return {
name: pkgInfo.name as string,
peerDepGraphHash: pkgInfo.peerDepGraphHash,
version: pkgSnapshot.version ?? pkgInfo.version as string ?? ('type' in pkgSnapshot.resolution && pkgSnapshot.resolution.type === 'nodeRuntime' ? depPath.replace(/^node@runtime:/, '') : undefined),
version: pkgSnapshot.version ?? pkgInfo.version as string ?? undefined,
nonSemverVersion: pkgInfo.nonSemverVersion,
}
}

View File

@@ -7,8 +7,5 @@ export function packageIdFromSnapshot (
pkgSnapshot: PackageSnapshot
): PkgId {
if (pkgSnapshot.id) return pkgSnapshot.id as PkgId
if (depPath.startsWith('node@runtime:')) {
return depPath as unknown as PkgId
}
return dp.tryGetPackageId(depPath) ?? depPath
}

View File

@@ -19,6 +19,10 @@ export const FULL_FILTERED_META_DIR = 'metadata-v1.3'
export const USEFUL_NON_ROOT_PNPM_FIELDS = ['executionEnv'] as const
export function getNodeBinLocationForCurrentOS (): string {
return process.platform === 'win32' ? 'node.exe' : 'bin/node'
export function getNodeBinLocationForCurrentOS (platform: string = process.platform): string {
return platform === 'win32' ? 'node.exe' : 'bin/node'
}
export function getDenoBinLocationForCurrentOS (platform: string = process.platform): string {
return platform === 'win32' ? 'deno.exe' : 'deno'
}

View File

@@ -80,7 +80,12 @@ export function tryGetPackageId (relDepPath: DepPath): PkgId {
pkgId = pkgId.substring(0, sepIndex)
}
if (pkgId.includes(':')) {
pkgId = pkgId.substring(pkgId.indexOf('@', 1) + 1)
const newPkgId = pkgId.substring(pkgId.indexOf('@', 1) + 1)
// TODO: change the format of package ID to always start with the package name.
// not only in the case of "runtime:"
if (!newPkgId.startsWith('runtime:')) {
pkgId = newPkgId
}
}
return pkgId as PkgId
}

View File

@@ -37,6 +37,7 @@
"@pnpm/directory-fetcher": "workspace:*",
"@pnpm/fetch": "workspace:*",
"@pnpm/fetching-types": "workspace:*",
"@pnpm/fetching.binary-fetcher": "workspace:*",
"@pnpm/git-fetcher": "workspace:*",
"@pnpm/network.auth-header": "workspace:*",
"@pnpm/node.fetcher": "workspace:*",

View File

@@ -6,12 +6,12 @@ import {
import { type AgentOptions, createFetchFromRegistry } from '@pnpm/fetch'
import { type SslConfig } from '@pnpm/types'
import { type FetchFromRegistry, type GetAuthHeader, type RetryTimeoutOptions } from '@pnpm/fetching-types'
import type { CustomFetchers, GitFetcher, DirectoryFetcher, NodeRuntimeFetcher } from '@pnpm/fetcher-base'
import type { CustomFetchers, GitFetcher, DirectoryFetcher, BinaryFetcher } from '@pnpm/fetcher-base'
import { createDirectoryFetcher } from '@pnpm/directory-fetcher'
import { createGitFetcher } from '@pnpm/git-fetcher'
import { createTarballFetcher, type TarballFetchers } from '@pnpm/tarball-fetcher'
import { createGetAuthHeaderByURI } from '@pnpm/network.auth-header'
import { createNodeRuntimeFetcher } from '@pnpm/node.fetcher'
import { createBinaryFetcher } from '@pnpm/fetching.binary-fetcher'
import mapValues from 'ramda/src/map'
export type { ResolveFunction }
@@ -59,7 +59,7 @@ export function createResolver (opts: ClientOptions): { resolve: ResolveFunction
type Fetchers = {
git: GitFetcher
directory: DirectoryFetcher
nodeRuntime: NodeRuntimeFetcher
binary: BinaryFetcher
} & TarballFetchers
function createFetchers (
@@ -73,7 +73,7 @@ function createFetchers (
...tarballFetchers,
...createGitFetcher(opts),
...createDirectoryFetcher({ resolveSymlinks: opts.resolveSymlinksInInjectedDirs, includeOnlyPackageFiles: opts.includeOnlyPackageFiles }),
...createNodeRuntimeFetcher({
...createBinaryFetcher({
fetch: fetchFromRegistry,
fetchFromRemoteTarball: tarballFetchers.remoteTarball,
offline: opts.offline,

View File

@@ -12,6 +12,9 @@
{
"path": "../../env/node.fetcher"
},
{
"path": "../../fetching/binary-fetcher"
},
{
"path": "../../fetching/directory-fetcher"
},

View File

@@ -0,0 +1,237 @@
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/denoland/deno/releases/download/v2.4.2/deno-aarch64-apple-darwin.zip',
integrity: 'sha256-cy885Q3GSmOXLKTvtIZ5KZwBZjzpGPcQ1pWmjOX0yTY=',
bin: 'deno',
},
},
{
targets: [
{
os: 'linux',
cpu: 'arm64',
},
],
resolution: {
type: 'binary',
archive: 'zip',
url: 'https://github.com/denoland/deno/releases/download/v2.4.2/deno-aarch64-unknown-linux-gnu.zip',
integrity: 'sha256-SjIY48qZ8qu8QdIGkbynlC0Y68sB22tDicu5HqvxBV8=',
bin: 'deno',
},
},
{
targets: [
{
os: 'darwin',
cpu: 'x64',
},
],
resolution: {
type: 'binary',
archive: 'zip',
url: 'https://github.com/denoland/deno/releases/download/v2.4.2/deno-x86_64-apple-darwin.zip',
integrity: 'sha256-+kfrcrjR80maf7Pmx7vNOx5kBxErsD+v1AqoA4pUuT4=',
bin: 'deno',
},
},
{
targets: [
{
os: 'win32',
cpu: 'x64',
},
{
os: 'win32',
cpu: 'arm64',
},
],
resolution: {
type: 'binary',
archive: 'zip',
url: 'https://github.com/denoland/deno/releases/download/v2.4.2/deno-x86_64-pc-windows-msvc.zip',
integrity: 'sha256-WoyBb25yA3inTCVnZ5uip5nIFbjC/8BrDnHabCqb8Yk=',
bin: 'deno.exe',
},
},
{
targets: [
{
os: 'linux',
cpu: 'x64',
},
],
resolution: {
type: 'binary',
archive: 'zip',
url: 'https://github.com/denoland/deno/releases/download/v2.4.2/deno-x86_64-unknown-linux-gnu.zip',
integrity: 'sha256-2Ed4YzIVt8uTz3aQhg1iQfYysIe9KhneEs1BDmsuFXo=',
bin: 'deno',
},
},
]
test('installing Deno runtime', async () => {
const project = prepareEmpty()
const { updatedManifest: manifest } = await addDependenciesToPackage({}, ['deno@runtime:2.4.2'], testDefaults({ fastUnpack: false }))
project.isExecutable('.bin/deno')
expect(project.readLockfile()).toStrictEqual({
settings: {
autoInstallPeers: true,
excludeLinksFromLockfile: false,
},
importers: {
'.': {
dependencies: {
deno: {
specifier: 'runtime:2.4.2',
version: 'runtime:2.4.2',
},
},
},
},
lockfileVersion: LOCKFILE_VERSION,
packages: {
'deno@runtime:2.4.2': {
hasBin: true,
resolution: {
type: 'variations',
variants: RESOLUTIONS,
},
version: '2.4.2',
},
},
snapshots: {
'deno@runtime:2.4.2': {},
},
})
rimraf('node_modules')
await install(manifest, testDefaults({ frozenLockfile: true }, {
offline: true, // We want to verify that Deno is resolved from cache.
}))
project.isExecutable('.bin/deno')
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: {
deno: {
specifier: 'runtime:2.4.2',
version: 'runtime:2.4.2',
},
'@pnpm.e2e/dep-of-pkg-with-1-dep': {
specifier: '100.1.0',
version: '100.1.0',
},
},
},
},
lockfileVersion: LOCKFILE_VERSION,
packages: {
'deno@runtime:2.4.2': {
hasBin: true,
resolution: {
type: 'variations',
variants: RESOLUTIONS,
},
version: '2.4.2',
},
'@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: {
'deno@runtime:2.4.2': {},
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0': {},
},
})
})
test('installing Deno runtime fails if offline mode is used and Deno not found locally', async () => {
prepareEmpty()
await expect(
addDependenciesToPackage({}, ['deno@runtime:2.4.2'], testDefaults({ fastUnpack: false }, { offline: true }))
).rejects.toThrow(/Failed to resolve deno@2.4.2 in package mirror/)
})
test('installing Deno runtime fails if integrity check fails', async () => {
prepareEmpty()
writeYamlFile(WANTED_LOCKFILE, {
settings: {
autoInstallPeers: true,
excludeLinksFromLockfile: false,
},
importers: {
'.': {
devDependencies: {
deno: {
specifier: 'runtime:2.4.2',
version: 'runtime:2.4.2',
},
},
},
},
lockfileVersion: LOCKFILE_VERSION,
packages: {
'deno@runtime:2.4.2': {
hasBin: true,
resolution: {
type: 'variations',
variants: RESOLUTIONS.map((resolutionVariant) => ({
...resolutionVariant,
resolution: {
...resolutionVariant.resolution,
integrity: 'sha256-0000000000000000000000000000000000000000000=',
},
})),
},
version: '2.4.2',
},
},
snapshots: {
'deno@runtime:2.4.2': {},
},
}, {
lineWidth: -1,
})
const manifest = {
devDependencies: {
deno: 'runtime:2.4.2',
},
}
await expect(install(manifest, testDefaults({ frozenLockfile: true }, {
retry: {
retries: 0,
},
}))).rejects.toThrow(/Got unexpected checksum for/)
})

View File

@@ -6,19 +6,176 @@ import { sync as rimraf } from '@zkochan/rimraf'
import { sync as writeYamlFile } from 'write-yaml-file'
import { testDefaults } from '../utils'
const NODE_INTEGRITIES = {
'aix-ppc64': 'sha256-13Q/3fXoZxJPVVqR9scpEE/Vx12TgvEChsP7s/0S7wc=',
'darwin-arm64': 'sha256-6pbTSc+qZ6qHzuqj5bUskWf3rDAv2NH/Fi0HhencB4U=',
'darwin-x64': 'sha256-Qio4h/9UGPCkVS2Jz5k0arirUbtdOEZguqiLhETSwRE=',
'linux-arm64': 'sha256-HTVHImvn5ZrO7lx9Aan4/BjeZ+AVxaFdjPOFtuAtBis=',
'linux-armv7l': 'sha256-0h239Xxc4YKuwrmoPjKVq8N+FzGrtzmV09Vz4EQJl3w=',
'linux-ppc64le': 'sha256-OwmNzPVtRGu7gIRdNbvsvbdGEoYNFpDzohY4fJnJ1iA=',
'linux-s390x': 'sha256-fsX9rQyBnuoXkA60PB3pSNYgp4OxrJQGLKpDh3ipKzA=',
'linux-x64': 'sha256-dLsPOoAwfFKUIcPthFF7j1Q4Z3CfQeU81z35nmRCr00=',
'win32-arm64': 'sha256-N2Ehz0a9PAJcXmetrhkK/14l0zoLWPvA2GUtczULOPA=',
'win32-x64': 'sha256-MtY5tH1MCmUf+PjX1BpFQWij1ARb43mF+agQz4zvYXQ=',
'win32-x86': 'sha256-4BNPUBcVSjN2csf7zRVOKyx3S0MQkRhWAZINY9DEt9A=',
}
const RESOLUTIONS = [
{
targets: [
{
os: 'aix',
cpu: 'ppc64',
},
],
resolution: {
type: 'binary',
archive: 'tarball',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-aix-ppc64.tar.gz',
integrity: 'sha256-13Q/3fXoZxJPVVqR9scpEE/Vx12TgvEChsP7s/0S7wc=',
bin: 'bin/node',
},
},
{
targets: [
{
os: 'darwin',
cpu: 'arm64',
},
],
resolution: {
type: 'binary',
archive: 'tarball',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-darwin-arm64.tar.gz',
integrity: 'sha256-6pbTSc+qZ6qHzuqj5bUskWf3rDAv2NH/Fi0HhencB4U=',
bin: 'bin/node',
},
},
{
targets: [
{
os: 'darwin',
cpu: 'x64',
},
],
resolution: {
type: 'binary',
archive: 'tarball',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-darwin-x64.tar.gz',
integrity: 'sha256-Qio4h/9UGPCkVS2Jz5k0arirUbtdOEZguqiLhETSwRE=',
bin: 'bin/node',
},
},
{
targets: [
{
os: 'linux',
cpu: 'arm64',
},
],
resolution: {
type: 'binary',
archive: 'tarball',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-linux-arm64.tar.gz',
integrity: 'sha256-HTVHImvn5ZrO7lx9Aan4/BjeZ+AVxaFdjPOFtuAtBis=',
bin: 'bin/node',
},
},
{
targets: [
{
os: 'linux',
cpu: 'armv7l',
},
],
resolution: {
type: 'binary',
archive: 'tarball',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-linux-armv7l.tar.gz',
integrity: 'sha256-0h239Xxc4YKuwrmoPjKVq8N+FzGrtzmV09Vz4EQJl3w=',
bin: 'bin/node',
},
},
{
targets: [
{
os: 'linux',
cpu: 'ppc64le',
},
],
resolution: {
type: 'binary',
archive: 'tarball',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-linux-ppc64le.tar.gz',
integrity: 'sha256-OwmNzPVtRGu7gIRdNbvsvbdGEoYNFpDzohY4fJnJ1iA=',
bin: 'bin/node',
},
},
{
targets: [
{
os: 'linux',
cpu: 's390x',
},
],
resolution: {
type: 'binary',
archive: 'tarball',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-linux-s390x.tar.gz',
integrity: 'sha256-fsX9rQyBnuoXkA60PB3pSNYgp4OxrJQGLKpDh3ipKzA=',
bin: 'bin/node',
},
},
{
targets: [
{
os: 'linux',
cpu: 'x64',
},
],
resolution: {
type: 'binary',
archive: 'tarball',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-linux-x64.tar.gz',
integrity: 'sha256-dLsPOoAwfFKUIcPthFF7j1Q4Z3CfQeU81z35nmRCr00=',
bin: 'bin/node',
},
},
{
targets: [
{
os: 'win32',
cpu: 'arm64',
},
],
resolution: {
type: 'binary',
archive: 'zip',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-win-arm64.zip',
integrity: 'sha256-N2Ehz0a9PAJcXmetrhkK/14l0zoLWPvA2GUtczULOPA=',
bin: 'node.exe',
prefix: 'node-v22.0.0-win-arm64',
},
},
{
targets: [
{
os: 'win32',
cpu: 'x64',
},
],
resolution: {
type: 'binary',
archive: 'zip',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-win-x64.zip',
integrity: 'sha256-MtY5tH1MCmUf+PjX1BpFQWij1ARb43mF+agQz4zvYXQ=',
bin: 'node.exe',
prefix: 'node-v22.0.0-win-x64',
},
},
{
targets: [
{
os: 'win32',
cpu: 'x86',
},
],
resolution: {
type: 'binary',
archive: 'zip',
url: 'https://nodejs.org/download/release/v22.0.0/node-v22.0.0-win-x86.zip',
integrity: 'sha256-4BNPUBcVSjN2csf7zRVOKyx3S0MQkRhWAZINY9DEt9A=',
bin: 'node.exe',
prefix: 'node-v22.0.0-win-x86',
},
},
]
test('installing Node.js runtime', async () => {
const project = prepareEmpty()
@@ -45,9 +202,10 @@ test('installing Node.js runtime', async () => {
'node@runtime:22.0.0': {
hasBin: true,
resolution: {
integrities: NODE_INTEGRITIES,
type: 'nodeRuntime',
type: 'variations',
variants: RESOLUTIONS,
},
version: '22.0.0',
},
},
snapshots: {
@@ -88,9 +246,10 @@ test('installing Node.js runtime', async () => {
'node@runtime:22.0.0': {
hasBin: true,
resolution: {
integrities: NODE_INTEGRITIES,
type: 'nodeRuntime',
type: 'variations',
variants: RESOLUTIONS,
},
version: '22.0.0',
},
'@pnpm.e2e/dep-of-pkg-with-1-dep@100.1.0': {
resolution: {
@@ -142,17 +301,23 @@ test('installing Node.js runtime fails if integrity check fails', async () => {
'node@runtime:22.0.0': {
hasBin: true,
resolution: {
integrities: {
...NODE_INTEGRITIES,
[`${process.platform}-${process.arch}`]: 'sha256-0000000000000000000000000000000000000000000=',
},
type: 'nodeRuntime',
type: 'variations',
variants: RESOLUTIONS.map((resolutionVariant) => ({
...resolutionVariant,
resolution: {
...resolutionVariant.resolution,
integrity: 'sha256-0000000000000000000000000000000000000000000=',
},
})),
},
version: '22.0.0',
},
},
snapshots: {
'node@runtime:22.0.0': {},
},
}, {
lineWidth: -1,
})
const manifest = {

View File

@@ -1,7 +1,7 @@
import { promises as fs, existsSync } from 'fs'
import Module from 'module'
import path from 'path'
import { getNodeBinLocationForCurrentOS } from '@pnpm/constants'
import { getNodeBinLocationForCurrentOS, getDenoBinLocationForCurrentOS } from '@pnpm/constants'
import { PnpmError } from '@pnpm/error'
import { logger, globalWarn } from '@pnpm/logger'
import { getAllDependenciesFromManifest } from '@pnpm/manifest-utils'
@@ -206,7 +206,10 @@ async function getPackageBins (
: await safeReadPkgJson(target)
if (manifest == null) {
if (path.basename(target) === 'node') {
// There is a probably a better way to do this.
// It isn't good to have these hardcoded here.
switch (path.basename(target)) {
case 'node':
return [{
name: 'node',
path: path.join(target, getNodeBinLocationForCurrentOS()),
@@ -215,6 +218,15 @@ async function getPackageBins (
pkgVersion: '',
makePowerShellShim: false,
}]
case 'deno':
return [{
name: 'deno',
path: path.join(target, getDenoBinLocationForCurrentOS()),
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.

View File

@@ -21,13 +21,14 @@ import { globalWarn, logger } from '@pnpm/logger'
import { packageIsInstallable } from '@pnpm/package-is-installable'
import { readPackageJson } from '@pnpm/read-package-json'
import {
type PlatformAssetResolution,
type DirectoryResolution,
type NodeRuntimeResolution,
type PreferredVersions,
type Resolution,
type ResolveFunction,
type ResolveResult,
type TarballResolution,
type AtomicResolution,
} from '@pnpm/resolver-base'
import {
type BundledManifest,
@@ -235,7 +236,7 @@ async function resolveAndFetch (
const id = pkgId!
if (resolution.type === 'directory' && !id.startsWith('file:')) {
if ('type' in resolution && resolution.type === 'directory' && !id.startsWith('file:')) {
if (manifest == null) {
throw new Error(`Couldn't read package.json of local dependency ${wantedDependency.alias ? wantedDependency.alias + '@' : ''}${wantedDependency.bareSpecifier ?? ''}`)
}
@@ -332,6 +333,12 @@ interface FetchLock {
fetchRawManifest?: boolean
}
interface GetFilesIndexFilePathResult {
target: string
filesIndexFile: string
resolution: AtomicResolution
}
function getFilesIndexFilePath (
ctx: {
getIndexFilePathInCafs: (integrity: string, pkgId: string) => string
@@ -339,15 +346,41 @@ function getFilesIndexFilePath (
virtualStoreDirMaxLength: number
},
opts: Pick<FetchPackageToStoreOptions, 'pkg' | 'ignoreScripts'>
) {
): GetFilesIndexFilePathResult {
const targetRelative = depPathToFilename(opts.pkg.id, ctx.virtualStoreDirMaxLength)
const target = path.join(ctx.storeDir, targetRelative)
const filesIndexFile = (opts.pkg.resolution as TarballResolution).integrity
? ctx.getIndexFilePathInCafs((opts.pkg.resolution as TarballResolution).integrity!, opts.pkg.id)
: (opts.pkg.resolution as NodeRuntimeResolution).integrities?.[`${process.platform}-${process.arch}`]
? ctx.getIndexFilePathInCafs((opts.pkg.resolution as NodeRuntimeResolution).integrities[`${process.platform}-${process.arch}`], opts.pkg.id)
: path.join(target, opts.ignoreScripts ? 'integrity-not-built.json' : 'integrity.json')
return { filesIndexFile, target }
if ((opts.pkg.resolution as TarballResolution).integrity) {
return {
target,
filesIndexFile: ctx.getIndexFilePathInCafs((opts.pkg.resolution as TarballResolution).integrity!, opts.pkg.id),
resolution: opts.pkg.resolution as AtomicResolution,
}
}
let resolution!: AtomicResolution
if (opts.pkg.resolution.type === 'variations') {
resolution = findResolution(opts.pkg.resolution.variants)
if ((resolution as TarballResolution).integrity) {
return {
target,
filesIndexFile: ctx.getIndexFilePathInCafs((resolution as TarballResolution).integrity!, opts.pkg.id),
resolution,
}
}
} else {
resolution = opts.pkg.resolution
}
const filesIndexFile = path.join(target, opts.ignoreScripts ? 'integrity-not-built.json' : 'integrity.json')
return { filesIndexFile, target, resolution }
}
function findResolution (resolutionVariants: PlatformAssetResolution[]): AtomicResolution {
const resolutionVariant = resolutionVariants
.find((resolutionVariant) => resolutionVariant.targets.some((target) => target.os === process.platform && target.cpu === process.arch))
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)}`)
}
return resolutionVariant.resolution
}
function fetchToStore (
@@ -358,7 +391,7 @@ function fetchToStore (
) => Promise<{ verified: boolean, pkgFilesIndex: PackageFilesIndex, manifest?: DependencyManifest, requiresBuild: boolean }>
fetch: (
packageId: string,
resolution: Resolution,
resolution: AtomicResolution,
opts: FetchOptions
) => Promise<FetchResult>
fetchingLocker: Map<string, FetchLock>
@@ -384,9 +417,9 @@ function fetchToStore (
if (!ctx.fetchingLocker.has(opts.pkg.id)) {
const fetching = pDefer<PkgRequestFetchResult>()
const { filesIndexFile, target } = getFilesIndexFilePath(ctx, opts)
const { filesIndexFile, target, resolution } = getFilesIndexFilePath(ctx, opts)
doFetchToStore(filesIndexFile, fetching, target) // eslint-disable-line
doFetchToStore(filesIndexFile, fetching, target, resolution) // eslint-disable-line
ctx.fetchingLocker.set(opts.pkg.id, {
fetching: removeKeyOnFail(fetching.promise),
@@ -482,11 +515,12 @@ function fetchToStore (
async function doFetchToStore (
filesIndexFile: string,
fetching: pDefer.DeferredPromise<PkgRequestFetchResult>,
target: string
target: string,
resolution: AtomicResolution
) {
try {
const isLocalTarballDep = opts.pkg.id.startsWith('file:')
const isLocalPkg = opts.pkg.resolution.type === 'directory'
const isLocalPkg = resolution.type === 'directory'
if (
!opts.force &&
@@ -557,7 +591,7 @@ Actual package in the store with the given integrity: ${pkgFilesIndex.name}@${pk
const fetchedPackage = await ctx.requestsQueue.add(async () => ctx.fetch(
opts.pkg.id,
opts.pkg.resolution,
resolution,
{
filesIndexFile,
lockfileDir: opts.lockfileDir,
@@ -638,7 +672,7 @@ async function fetcher (
fetcherByHostingType: Fetchers,
cafs: Cafs,
packageId: string,
resolution: Resolution,
resolution: AtomicResolution,
opts: FetchOptions
): Promise<FetchResult> {
const fetch = pickFetcher(fetcherByHostingType, resolution)

View File

@@ -80,7 +80,13 @@ function toLockfileDependency (
if (opts.depPath.includes(':')) {
// There is no guarantee that a non-npmjs.org-hosted package is going to have a version field.
// Also, for local directory dependencies, the version is not needed.
if (pkg.version && (!('type' in lockfileResolution) || lockfileResolution.type !== 'directory' && lockfileResolution.type !== 'nodeRuntime')) {
if (
pkg.version &&
(
!('type' in lockfileResolution) ||
lockfileResolution.type !== 'directory'
)
) {
result['version'] = pkg.version
}
}

View File

@@ -235,40 +235,54 @@ function convertManifestAfterRead (manifest: ProjectManifest): ProjectManifest {
}
}
}
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}`
}
}
}
return manifest
}
function convertManifestBeforeWrite (manifest: ProjectManifest): ProjectManifest {
const nodeDep = manifest.devDependencies?.['node']
if (typeof nodeDep === 'string' && nodeDep.startsWith('runtime:')) {
const version = nodeDep.replace(/^runtime:/, '')
manifest.devEngines ??= {}
for (const runtimeName of ['node', 'deno']) {
const nodeDep = manifest.devDependencies?.[runtimeName]
if (typeof nodeDep === 'string' && nodeDep.startsWith('runtime:')) {
const version = nodeDep.replace(/^runtime:/, '')
manifest.devEngines ??= {}
const nodeRuntimeEntry: DevEngineDependency = {
name: 'node',
version,
onFail: 'download',
}
if (!manifest.devEngines.runtime) {
manifest.devEngines.runtime = nodeRuntimeEntry
} else if (Array.isArray(manifest.devEngines.runtime)) {
const existing = manifest.devEngines.runtime.find(({ name }) => name === 'node')
if (existing) {
Object.assign(existing, nodeRuntimeEntry)
} else {
manifest.devEngines.runtime.push(nodeRuntimeEntry)
const nodeRuntimeEntry: DevEngineDependency = {
name: runtimeName,
version,
onFail: 'download',
}
if (!manifest.devEngines.runtime) {
manifest.devEngines.runtime = nodeRuntimeEntry
} else if (Array.isArray(manifest.devEngines.runtime)) {
const existing = manifest.devEngines.runtime.find(({ name }) => name === runtimeName)
if (existing) {
Object.assign(existing, nodeRuntimeEntry)
} else {
manifest.devEngines.runtime.push(nodeRuntimeEntry)
}
} else if (manifest.devEngines.runtime.name === runtimeName) {
Object.assign(manifest.devEngines.runtime, nodeRuntimeEntry)
} else {
manifest.devEngines.runtime = [
manifest.devEngines.runtime,
nodeRuntimeEntry,
]
}
if (manifest.devDependencies) {
delete manifest.devDependencies[runtimeName]
}
} else if (manifest.devEngines.runtime.name === 'node') {
Object.assign(manifest.devEngines.runtime, nodeRuntimeEntry)
} else {
manifest.devEngines.runtime = [
manifest.devEngines.runtime,
nodeRuntimeEntry,
]
}
if (manifest.devDependencies) {
delete manifest.devDependencies['node']
}
}
return manifest

363
pnpm-lock.yaml generated
View File

@@ -388,8 +388,8 @@ catalogs:
specifier: ^29.7.0
version: 29.7.0
js-yaml:
specifier: npm:@zkochan/js-yaml@0.0.7
version: 0.0.7
specifier: npm:@zkochan/js-yaml@0.0.8
version: 0.0.8
json5:
specifier: ^2.2.3
version: 2.2.3
@@ -671,7 +671,7 @@ overrides:
hosted-git-info@1: npm:@pnpm/hosted-git-info@1.0.0
http-proxy-middleware@<2.0.7: ^2.0.7
istanbul-reports: npm:@zkochan/istanbul-reports
js-yaml@^4.0.0: npm:@zkochan/js-yaml@0.0.7
js-yaml@^4.0.0: npm:@zkochan/js-yaml@0.0.8
json5@<2.2.2: ^2.2.3
jsonwebtoken@<=8.5.1: '>=9.0.0'
nopt@5: npm:@pnpm/nopt@^0.2.1
@@ -810,7 +810,7 @@ importers:
version: link:../packages/logger
'@pnpm/meta-updater':
specifier: 'catalog:'
version: 2.0.6(@types/node@22.15.29)(typanion@3.14.0)
version: 2.0.6(@types/node@18.19.34)(typanion@3.14.0)
'@pnpm/object.key-sorting':
specifier: workspace:*
version: link:../object/key-sorting
@@ -2067,9 +2067,6 @@ importers:
env/node.fetcher:
dependencies:
'@pnpm/constants':
specifier: workspace:*
version: link:../../packages/constants
'@pnpm/create-cafs-store':
specifier: workspace:*
version: link:../../store/create-cafs-store
@@ -2079,36 +2076,21 @@ importers:
'@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.resolver':
specifier: workspace:*
version: link:../node.resolver
'@pnpm/tarball-fetcher':
specifier: workspace:*
version: link:../../fetching/tarball-fetcher
'@pnpm/worker':
specifier: workspace:*
version: link:../../worker
adm-zip:
specifier: 'catalog:'
version: 0.5.16
detect-libc:
specifier: 'catalog:'
version: 2.0.3
rename-overwrite:
specifier: 'catalog:'
version: 6.0.2
ssri:
specifier: 'catalog:'
version: 10.0.5
tempy:
specifier: 'catalog:'
version: 1.0.1
devDependencies:
'@pnpm/cafs-types':
specifier: workspace:*
@@ -2122,9 +2104,9 @@ importers:
'@types/adm-zip':
specifier: 'catalog:'
version: 0.5.7
'@types/ssri':
adm-zip:
specifier: 'catalog:'
version: 7.1.5
version: 0.5.16
node-fetch:
specifier: 'catalog:'
version: '@pnpm/node-fetch@1.0.0'
@@ -2874,6 +2856,43 @@ importers:
specifier: 'catalog:'
version: 6.0.6
fetching/binary-fetcher:
dependencies:
'@pnpm/error':
specifier: workspace:*
version: link:../../packages/error
'@pnpm/fetcher-base':
specifier: workspace:*
version: link:../fetcher-base
'@pnpm/fetching-types':
specifier: workspace:*
version: link:../../network/fetching-types
'@pnpm/worker':
specifier: workspace:*
version: link:../../worker
adm-zip:
specifier: 'catalog:'
version: 0.5.16
rename-overwrite:
specifier: 'catalog:'
version: 6.0.2
ssri:
specifier: 'catalog:'
version: 10.0.5
tempy:
specifier: 'catalog:'
version: 1.0.1
devDependencies:
'@pnpm/fetching.binary-fetcher':
specifier: workspace:*
version: 'link:'
'@types/adm-zip':
specifier: 'catalog:'
version: 0.5.7
'@types/ssri':
specifier: 'catalog:'
version: 7.1.5
fetching/directory-fetcher:
dependencies:
'@pnpm/exec.pkg-requires-build':
@@ -3479,7 +3498,7 @@ importers:
version: 1.0.0
js-yaml:
specifier: 'catalog:'
version: '@zkochan/js-yaml@0.0.7'
version: '@zkochan/js-yaml@0.0.8'
normalize-path:
specifier: 'catalog:'
version: 3.0.0
@@ -3751,6 +3770,9 @@ importers:
'@pnpm/patching.types':
specifier: workspace:*
version: link:../../patching/types
'@pnpm/resolver-base':
specifier: workspace:*
version: link:../../resolving/resolver-base
'@pnpm/types':
specifier: workspace:*
version: link:../../packages/types
@@ -4511,6 +4533,9 @@ importers:
'@pnpm/fetching-types':
specifier: workspace:*
version: link:../../network/fetching-types
'@pnpm/fetching.binary-fetcher':
specifier: workspace:*
version: link:../../fetching/binary-fetcher
'@pnpm/git-fetcher':
specifier: workspace:*
version: link:../../fetching/git-fetcher
@@ -6731,6 +6756,9 @@ importers:
'@pnpm/resolver-base':
specifier: workspace:*
version: link:../resolver-base
'@pnpm/resolving.deno-resolver':
specifier: workspace:*
version: link:../deno-resolver
'@pnpm/tarball-resolver':
specifier: workspace:*
version: link:../tarball-resolver
@@ -6742,6 +6770,52 @@ importers:
specifier: workspace:*
version: link:../../network/fetch
resolving/deno-resolver:
dependencies:
'@pnpm/constants':
specifier: workspace:*
version: link:../../packages/constants
'@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.deno-resolver':
specifier: workspace:*
version: 'link:'
'@types/semver':
specifier: 'catalog:'
version: 7.5.3
resolving/git-resolver:
dependencies:
'@pnpm/fetch':
@@ -10527,8 +10601,8 @@ packages:
resolution: {integrity: sha512-GeZZ9YNlvveT1CrMqe64s9+5hNF5p9Gj6J+PRQU3OnBS/9c2y5kSFavUZ9YAuoAgxFW8tKGKWIxJK4I6HBSigg==}
engines: {node: '>=8'}
'@zkochan/js-yaml@0.0.7':
resolution: {integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==}
'@zkochan/js-yaml@0.0.8':
resolution: {integrity: sha512-8EImghMs6Nr1EfYCqBFJsBsFei1xV3P0MziORNAYQQ8lGeou86Hdy1xUYJJi+PWw6cUbrCdWRjr3oXmPC/uckw==}
hasBin: true
'@zkochan/retry@0.2.0':
@@ -16353,7 +16427,7 @@ snapshots:
globals: 13.24.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: '@zkochan/js-yaml@0.0.7'
js-yaml: '@zkochan/js-yaml@0.0.8'
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
@@ -16367,7 +16441,7 @@ snapshots:
globals: 14.0.0
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: '@zkochan/js-yaml@0.0.7'
js-yaml: '@zkochan/js-yaml@0.0.8'
minimatch: 3.1.2
strip-json-comments: 3.1.1
transitivePeerDependencies:
@@ -16711,28 +16785,6 @@ snapshots:
- supports-color
- typanion
'@pnpm/cli-utils@1000.1.5(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)':
dependencies:
'@pnpm/cli-meta': 1000.0.8
'@pnpm/config': 1003.1.1(@pnpm/logger@1001.0.0)
'@pnpm/config.deps-installer': 1000.0.5(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))
'@pnpm/default-reporter': 1002.0.1(@pnpm/logger@1001.0.0)
'@pnpm/error': 1000.0.2
'@pnpm/logger': 1001.0.0
'@pnpm/manifest-utils': 1001.0.1(@pnpm/logger@1001.0.0)
'@pnpm/package-is-installable': 1000.0.10(@pnpm/logger@1001.0.0)
'@pnpm/pnpmfile': 1001.2.2(@pnpm/logger@1001.0.0)
'@pnpm/read-project-manifest': 1000.0.11
'@pnpm/store-connection-manager': 1002.0.3(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)
'@pnpm/types': 1000.6.0
chalk: 4.1.2
load-json-file: 6.2.0
transitivePeerDependencies:
- '@pnpm/worker'
- domexception
- supports-color
- typanion
'@pnpm/client@1000.0.19(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@18.19.34))(typanion@3.14.0)':
dependencies:
'@pnpm/default-resolver': 1002.0.2(@pnpm/logger@1001.0.0)
@@ -16752,25 +16804,6 @@ snapshots:
- supports-color
- typanion
'@pnpm/client@1000.0.19(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)':
dependencies:
'@pnpm/default-resolver': 1002.0.2(@pnpm/logger@1001.0.0)
'@pnpm/directory-fetcher': 1000.1.7(@pnpm/logger@1001.0.0)
'@pnpm/fetch': 1000.2.2(@pnpm/logger@1001.0.0)
'@pnpm/fetching-types': 1000.1.0
'@pnpm/git-fetcher': 1001.0.8(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)
'@pnpm/network.auth-header': 1000.0.3
'@pnpm/resolver-base': 1003.0.1
'@pnpm/tarball-fetcher': 1001.0.8(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)
'@pnpm/types': 1000.6.0
ramda: '@pnpm/ramda@0.28.1'
transitivePeerDependencies:
- '@pnpm/logger'
- '@pnpm/worker'
- domexception
- supports-color
- typanion
'@pnpm/colorize-semver-diff@1.0.1':
dependencies:
chalk: 4.1.2
@@ -16804,28 +16837,6 @@ snapshots:
- domexception
- supports-color
'@pnpm/config.deps-installer@1000.0.5(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))':
dependencies:
'@pnpm/config.config-writer': 1000.0.5
'@pnpm/core-loggers': 1001.0.1(@pnpm/logger@1001.0.0)
'@pnpm/error': 1000.0.2
'@pnpm/fetch': 1000.2.2(@pnpm/logger@1001.0.0)
'@pnpm/logger': 1001.0.0
'@pnpm/network.auth-header': 1000.0.3
'@pnpm/npm-resolver': 1004.0.1(@pnpm/logger@1001.0.0)
'@pnpm/package-store': 1002.0.4(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))
'@pnpm/parse-wanted-dependency': 1001.0.0
'@pnpm/pick-registry-for-package': 1000.0.8
'@pnpm/read-modules-dir': 1000.0.0
'@pnpm/read-package-json': 1000.0.9
'@pnpm/types': 1000.6.0
'@zkochan/rimraf': 3.0.2
get-npm-tarball-url: 2.1.0
transitivePeerDependencies:
- '@pnpm/worker'
- domexception
- supports-color
'@pnpm/config.env-replace@1.1.0': {}
'@pnpm/config.env-replace@3.0.1': {}
@@ -17135,19 +17146,6 @@ snapshots:
- supports-color
- typanion
'@pnpm/git-fetcher@1001.0.8(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)':
dependencies:
'@pnpm/fetcher-base': 1000.0.11
'@pnpm/fs.packlist': 2.0.0
'@pnpm/logger': 1001.0.0
'@pnpm/prepare-package': 1000.0.16(@pnpm/logger@1001.0.0)(typanion@3.14.0)
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.29)
'@zkochan/rimraf': 3.0.2
execa: safe-execa@0.1.2
transitivePeerDependencies:
- supports-color
- typanion
'@pnpm/git-resolver@1001.0.2(@pnpm/logger@1001.0.0)':
dependencies:
'@pnpm/fetch': 1000.2.2(@pnpm/logger@1001.0.0)
@@ -17294,24 +17292,6 @@ snapshots:
- supports-color
- typanion
'@pnpm/meta-updater@2.0.6(@types/node@22.15.29)(typanion@3.14.0)':
dependencies:
'@pnpm/find-workspace-dir': 1000.1.0
'@pnpm/logger': 1001.0.0
'@pnpm/types': 1000.6.0
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.29)
'@pnpm/workspace.find-packages': 1000.0.25(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)
'@pnpm/workspace.read-manifest': 1000.1.5
load-json-file: 7.0.1
meow: 11.0.0
print-diff: 2.0.0
write-json-file: 5.0.0
transitivePeerDependencies:
- '@types/node'
- domexception
- supports-color
- typanion
'@pnpm/network.agent@2.0.3':
dependencies:
'@pnpm/network.config': 2.1.0
@@ -17492,30 +17472,6 @@ snapshots:
semver: 7.7.2
ssri: 10.0.5
'@pnpm/package-requester@1004.0.2(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))':
dependencies:
'@pnpm/core-loggers': 1001.0.1(@pnpm/logger@1001.0.0)
'@pnpm/dependency-path': 1000.0.9
'@pnpm/error': 1000.0.2
'@pnpm/fetcher-base': 1000.0.11
'@pnpm/graceful-fs': 1000.0.0
'@pnpm/logger': 1001.0.0
'@pnpm/package-is-installable': 1000.0.10(@pnpm/logger@1001.0.0)
'@pnpm/pick-fetcher': 1000.0.0
'@pnpm/read-package-json': 1000.0.9
'@pnpm/resolver-base': 1003.0.1
'@pnpm/store-controller-types': 1003.0.2
'@pnpm/store.cafs': 1000.0.13
'@pnpm/types': 1000.6.0
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.29)
p-defer: 3.0.0
p-limit: 3.1.0
p-queue: 6.6.2
promise-share: 1.0.0
ramda: '@pnpm/ramda@0.28.1'
semver: 7.7.2
ssri: 10.0.5
'@pnpm/package-store@1002.0.4(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@18.19.34))':
dependencies:
'@pnpm/create-cafs-store': 1000.0.14(@pnpm/logger@1001.0.0)
@@ -17532,22 +17488,6 @@ snapshots:
ramda: '@pnpm/ramda@0.28.1'
ssri: 10.0.5
'@pnpm/package-store@1002.0.4(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))':
dependencies:
'@pnpm/create-cafs-store': 1000.0.14(@pnpm/logger@1001.0.0)
'@pnpm/fetcher-base': 1000.0.11
'@pnpm/logger': 1001.0.0
'@pnpm/package-requester': 1004.0.2(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))
'@pnpm/resolver-base': 1003.0.1
'@pnpm/store-controller-types': 1003.0.2
'@pnpm/store.cafs': 1000.0.13
'@pnpm/types': 1000.6.0
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.29)
'@zkochan/rimraf': 3.0.2
load-json-file: 6.2.0
ramda: '@pnpm/ramda@0.28.1'
ssri: 10.0.5
'@pnpm/parse-overrides@1000.0.2':
dependencies:
'@pnpm/catalogs.resolver': 1000.0.2
@@ -17764,25 +17704,6 @@ snapshots:
- supports-color
- typanion
'@pnpm/store-connection-manager@1002.0.3(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)':
dependencies:
'@pnpm/cli-meta': 1000.0.8
'@pnpm/client': 1000.0.19(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)
'@pnpm/config': 1003.1.1(@pnpm/logger@1001.0.0)
'@pnpm/error': 1000.0.2
'@pnpm/logger': 1001.0.0
'@pnpm/package-store': 1002.0.4(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))
'@pnpm/server': 1001.0.4(@pnpm/logger@1001.0.0)
'@pnpm/store-path': 1000.0.2
'@zkochan/diable': 1.0.2
delay: 5.0.0
dir-is-case-sensitive: 2.0.0
transitivePeerDependencies:
- '@pnpm/worker'
- domexception
- supports-color
- typanion
'@pnpm/store-controller-types@1001.0.3':
dependencies:
'@pnpm/fetcher-base': 1000.0.5
@@ -17856,28 +17777,6 @@ snapshots:
- supports-color
- typanion
'@pnpm/tarball-fetcher@1001.0.8(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)':
dependencies:
'@pnpm/core-loggers': 1001.0.1(@pnpm/logger@1001.0.0)
'@pnpm/error': 1000.0.2
'@pnpm/fetcher-base': 1000.0.11
'@pnpm/fetching-types': 1000.1.0
'@pnpm/fs.packlist': 2.0.0
'@pnpm/graceful-fs': 1000.0.0
'@pnpm/logger': 1001.0.0
'@pnpm/prepare-package': 1000.0.16(@pnpm/logger@1001.0.0)(typanion@3.14.0)
'@pnpm/worker': 1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.29)
'@zkochan/retry': 0.2.0
lodash.throttle: 4.1.1
p-map-values: 1.0.0
path-temp: 2.1.0
ramda: '@pnpm/ramda@0.28.1'
rename-overwrite: 6.0.3
transitivePeerDependencies:
- domexception
- supports-color
- typanion
'@pnpm/tarball-resolver@1002.0.2':
dependencies:
'@pnpm/fetching-types': 1000.1.0
@@ -17925,26 +17824,6 @@ snapshots:
transitivePeerDependencies:
- '@types/node'
'@pnpm/worker@1000.1.7(@pnpm/logger@packages+logger)(@types/node@22.15.29)':
dependencies:
'@pnpm/cafs-types': 1000.0.0
'@pnpm/create-cafs-store': 1000.0.14(@pnpm/logger@1001.0.0)
'@pnpm/crypto.polyfill': 1000.1.0
'@pnpm/error': 1000.0.2
'@pnpm/exec.pkg-requires-build': 1000.0.8
'@pnpm/fs.hard-link-dir': 1000.0.1(@pnpm/logger@1001.0.0)
'@pnpm/graceful-fs': 1000.0.0
'@pnpm/logger': link:packages/logger
'@pnpm/store.cafs': 1000.0.13
'@pnpm/symlink-dependency': 1000.0.9(@pnpm/logger@1001.0.0)
'@rushstack/worker-pool': 0.4.9(@types/node@22.15.29)
is-windows: 1.0.2
load-json-file: 6.2.0
p-limit: 3.1.0
shell-quote: 1.8.3
transitivePeerDependencies:
- '@types/node'
'@pnpm/workspace.find-packages@1000.0.15(@pnpm/logger@1000.0.0)':
dependencies:
'@pnpm/cli-utils': 1000.0.15(@pnpm/logger@1000.0.0)
@@ -17968,20 +17847,6 @@ snapshots:
- supports-color
- typanion
'@pnpm/workspace.find-packages@1000.0.25(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)':
dependencies:
'@pnpm/cli-utils': 1000.1.5(@pnpm/logger@1001.0.0)(@pnpm/worker@1000.1.7(@pnpm/logger@1001.0.0)(@types/node@22.15.29))(typanion@3.14.0)
'@pnpm/constants': 1001.1.0
'@pnpm/fs.find-packages': 1000.0.11
'@pnpm/logger': 1001.0.0
'@pnpm/types': 1000.6.0
'@pnpm/util.lex-comparator': 3.0.2
transitivePeerDependencies:
- '@pnpm/worker'
- domexception
- supports-color
- typanion
'@pnpm/workspace.manifest-writer@1000.1.4':
dependencies:
'@pnpm/constants': 1001.1.0
@@ -18687,7 +18552,7 @@ snapshots:
html-escaper: 2.0.2
istanbul-lib-report: 3.0.1
'@zkochan/js-yaml@0.0.7':
'@zkochan/js-yaml@0.0.8':
dependencies:
argparse: 2.0.1
@@ -19503,7 +19368,7 @@ snapshots:
cosmiconfig@8.3.6(typescript@5.5.4):
dependencies:
import-fresh: 3.3.1
js-yaml: '@zkochan/js-yaml@0.0.7'
js-yaml: '@zkochan/js-yaml@0.0.8'
parse-json: 5.2.0
path-type: 4.0.0
optionalDependencies:
@@ -20170,7 +20035,7 @@ snapshots:
imurmurhash: 0.1.4
is-glob: 4.0.3
is-path-inside: 3.0.3
js-yaml: '@zkochan/js-yaml@0.0.7'
js-yaml: '@zkochan/js-yaml@0.0.8'
json-stable-stringify-without-jsonify: 1.0.1
levn: 0.4.1
lodash.merge: 4.6.2
@@ -22526,7 +22391,7 @@ snapshots:
parse-npm-tarball-url@3.0.0:
dependencies:
semver: 7.7.1
semver: 7.7.2
parseurl@1.3.3: {}
@@ -22845,7 +22710,7 @@ snapshots:
read-yaml-file@2.1.0:
dependencies:
js-yaml: '@zkochan/js-yaml@0.0.7'
js-yaml: '@zkochan/js-yaml@0.0.8'
strip-bom: 4.0.0
readable-stream@2.3.8:
@@ -24021,7 +23886,7 @@ snapshots:
fast-safe-stringify: 2.1.1
handlebars: 4.7.7
http-errors: 2.0.0
js-yaml: '@zkochan/js-yaml@0.0.7'
js-yaml: '@zkochan/js-yaml@0.0.8'
jsonwebtoken: 9.0.0
kleur: 4.1.5
lodash: 4.17.21
@@ -24285,12 +24150,12 @@ snapshots:
write-yaml-file@4.2.0:
dependencies:
js-yaml: '@zkochan/js-yaml@0.0.7'
js-yaml: '@zkochan/js-yaml@0.0.8'
write-file-atomic: 3.0.3
write-yaml-file@5.0.0:
dependencies:
js-yaml: '@zkochan/js-yaml@0.0.7'
js-yaml: '@zkochan/js-yaml@0.0.8'
write-file-atomic: 5.0.1
xdg-basedir@5.1.0: {}

View File

@@ -120,7 +120,7 @@ catalog:
'@zkochan/rimraf': ^3.0.2
'@zkochan/table': ^2.0.1
dint: ^5.1.0
js-yaml: npm:@zkochan/js-yaml@0.0.7
js-yaml: npm:@zkochan/js-yaml@0.0.8
adm-zip: ^0.5.16
ansi-diff: ^1.2.0
archy: ^1.0.0

View File

@@ -191,7 +191,7 @@
"hosted-git-info@1": "npm:@pnpm/hosted-git-info@1.0.0",
"http-proxy-middleware@<2.0.7": "^2.0.7",
"istanbul-reports": "npm:@zkochan/istanbul-reports",
"js-yaml@^4.0.0": "npm:@zkochan/js-yaml@0.0.7",
"js-yaml@^4.0.0": "npm:@zkochan/js-yaml@0.0.8",
"json5@<2.2.2": "^2.2.3",
"jsonwebtoken@<=8.5.1": ">=9.0.0",
"nopt@5": "npm:@pnpm/nopt@^0.2.1",

View File

@@ -188,7 +188,7 @@ function convertPackageSnapshot (inputSnapshot: PackageSnapshot, opts: ConvertOp
const resolvedPath = path.resolve(opts.lockfileDir, inputResolution.directory)
const directory = normalizePath(path.relative(opts.deployDir, resolvedPath))
outputResolution = { ...inputResolution, directory }
} else if (inputResolution.type === 'git' || inputResolution.type === 'nodeRuntime') {
} else if (inputResolution.type === 'git' || inputResolution.type === 'variations') {
outputResolution = inputResolution
} else {
const resolution: never = inputResolution // `never` is the type guard to force fixing this code when adding new type of resolution

View File

@@ -40,6 +40,7 @@
"@pnpm/node.resolver": "workspace:*",
"@pnpm/npm-resolver": "workspace:*",
"@pnpm/resolver-base": "workspace:*",
"@pnpm/resolving.deno-resolver": "workspace:*",
"@pnpm/tarball-resolver": "workspace:*"
},
"devDependencies": {

View File

@@ -3,6 +3,7 @@ import { type FetchFromRegistry, type GetAuthHeader } from '@pnpm/fetching-types
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 {
createNpmResolver,
type JsrResolveResult,
@@ -35,6 +36,7 @@ export type DefaultResolveResult =
| TarballResolveResult
| WorkspaceResolveResult
| NodeRuntimeResolveResult
| DenoRuntimeResolveResult
export type DefaultResolver = (wantedDependency: WantedDependency, opts: ResolveOptions) => Promise<DefaultResolveResult>
@@ -51,6 +53,7 @@ export function createResolver (
preserveAbsolutePaths: pnpmOpts.preserveAbsolutePaths,
})
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 })
return {
resolve: async (wantedDependency, opts) => {
const resolution = await resolveFromNpm(wantedDependency, opts as ResolveFromNpmOptions) ??
@@ -60,7 +63,8 @@ export function createResolver (
await resolveFromGit(wantedDependency as { bareSpecifier: string }) ??
await _resolveFromLocal(wantedDependency as { bareSpecifier: string }, opts)
)) ??
await _resolveNodeRuntime(wantedDependency)
await _resolveNodeRuntime(wantedDependency) ??
await _resolveDenoRuntime(wantedDependency)
if (!resolution) {
throw new PnpmError(
'SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER',

View File

@@ -21,6 +21,9 @@
{
"path": "../../packages/error"
},
{
"path": "../deno-resolver"
},
{
"path": "../git-resolver"
},

View File

@@ -0,0 +1,16 @@
# @pnpm/resolving.deno-resolver
> Resolves the Deno runtime
[![npm version](https://img.shields.io/npm/v/@pnpm/resolving.deno-resolver.svg)](https://www.npmjs.com/package/@pnpm/resolving.deno-resolver)
## Installation
```sh
pnpm add @pnpm/resolving.deno-resolver
```
## License
MIT

View File

@@ -0,0 +1,58 @@
{
"name": "@pnpm/resolving.deno-resolver",
"version": "1000.0.0-0",
"description": "Resolves the Deno runtime",
"keywords": [
"pnpm",
"pnpm10",
"deno",
"runtime"
],
"license": "MIT",
"funding": "https://opencollective.com/pnpm",
"repository": "https://github.com/pnpm/pnpm/blob/main/resolving/deno-resolver",
"homepage": "https://github.com/pnpm/pnpm/blob/main/resolving/deno-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/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.deno-resolver": "workspace:*",
"@types/semver": "catalog:"
},
"engines": {
"node": ">=18.12"
},
"jest": {
"preset": "@pnpm/jest-config"
}
}

View File

@@ -0,0 +1,111 @@
import { getDenoBinLocationForCurrentOS } from '@pnpm/constants'
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'
const ASSET_REGEX = /^deno-(?<cpu>aarch64|x86_64)-(?<os>apple-darwin|unknown-linux-gnu|pc-windows-msvc)\.zip\.sha256sum$/
const OS_MAP = {
'apple-darwin': 'darwin',
'unknown-linux-gnu': 'linux',
'pc-windows-msvc': 'win32',
} as const
const CPU_MAP = {
aarch64: 'arm64',
x86_64: 'x64',
} as const
export interface DenoRuntimeResolveResult extends ResolveResult {
resolution: VariationsResolution
resolvedVia: 'github.com/denoland/deno'
}
export async function resolveDenoRuntime (
ctx: {
fetchFromRegistry: FetchFromRegistry
rawConfig: Record<string, string>
offline?: boolean
resolveFromNpm: NpmResolver
},
wantedDependency: WantedDependency
): Promise<DenoRuntimeResolveResult | null> {
if (wantedDependency.alias !== 'deno' || !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/denoland/deno/releases?per_page=100).
const npmResolution = await ctx.resolveFromNpm({ ...wantedDependency, bareSpecifier: versionSpec }, {})
if (npmResolution == null) {
throw new PnpmError('DENO_RESOLUTION_FAILURE', `Could not resolve Deno version specified as ${versionSpec}`)
}
const version = npmResolution.manifest.version
const res = await ctx.fetchFromRegistry(`https://api.github.com/repos/denoland/deno/releases/tags/v${version}`)
const data = (await res.json()) as { assets: Array<{ name: string, browser_download_url: string }> }
const assets: PlatformAssetResolution[] = []
await Promise.all(data.assets.map(async (asset) => {
const targets = parseAssetName(asset.name)
if (!targets) return
const sha256 = await fetchSha256(ctx.fetchFromRegistry, asset.browser_download_url)
const base64 = Buffer.from(sha256, 'hex').toString('base64')
assets.push({
targets,
resolution: {
type: 'binary',
url: asset.browser_download_url.replace(/\.sha256sum$/, ''),
integrity: `sha256-${base64}`,
bin: getDenoBinLocationForCurrentOS(targets[0].os),
archive: 'zip',
},
})
}))
assets.sort((asset1, asset2) => lexCompare((asset1.resolution as BinaryResolution).url, (asset2.resolution as BinaryResolution).url))
return {
id: `deno@runtime:${version}` as PkgResolutionId,
normalizedBareSpecifier: `runtime:${versionSpec}`,
resolvedVia: 'github.com/denoland/deno',
manifest: {
name: 'deno',
version,
bin: getDenoBinLocationForCurrentOS(),
},
resolution: {
type: 'variations',
variants: assets,
},
}
}
function parseAssetName (name: string): PlatformAssetTarget[] | null {
const m = ASSET_REGEX.exec(name)
if (!m?.groups) return null
const os = OS_MAP[m.groups.os as keyof typeof OS_MAP]
const cpu = CPU_MAP[m.groups.cpu as keyof typeof CPU_MAP]
const targets = [{ os, cpu }]
if (os === 'win32' && cpu === 'x64') {
// The Windows x64 binaries of Deno are compatible with arm64 architecture.
targets.push({ os: 'win32', cpu: 'arm64' })
}
return targets
}
async function fetchSha256 (fetch: FetchFromRegistry, url: string): Promise<string> {
const response = await fetch(url)
if (!response.ok) {
throw new PnpmError('DENO_GITHUB_FAILURE', `Failed to GET sha256 at ${url}`)
}
const txt = await response.text()
const m = txt.match(/([a-f0-9]{64})/i)
if (!m) {
throw new PnpmError('DENO_PARSE_HASH', `No SHA256 in ${url}`)
}
return m[1].toLowerCase()
}

View File

@@ -0,0 +1,43 @@
{
"extends": "@pnpm/tsconfig",
"compilerOptions": {
"outDir": "lib",
"rootDir": "src"
},
"include": [
"src/**/*.ts",
"../../__typings__/**/*.d.ts"
],
"references": [
{
"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": "../npm-resolver"
},
{
"path": "../resolver-base"
}
]
}

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"include": [
"src/**/*.ts",
"test/**/*.ts",
"../../__typings__/**/*.d.ts"
]
}

View File

@@ -17,6 +17,15 @@ export interface TarballResolution {
path?: string
}
export interface BinaryResolution {
type: 'binary'
archive: 'tarball' | 'zip'
url: string
integrity: string
bin: string
prefix?: string
}
/**
* directory on a file system
*/
@@ -32,16 +41,28 @@ export interface GitResolution {
type: 'git'
}
export interface NodeRuntimeResolution {
type: 'nodeRuntime'
integrities: Record<string, string>
export interface PlatformAssetTarget {
os: string
cpu: string
}
export type Resolution =
export interface PlatformAssetResolution {
resolution: AtomicResolution
targets: PlatformAssetTarget[]
}
export type AtomicResolution =
| TarballResolution
| DirectoryResolution
| GitResolution
| NodeRuntimeResolution
| BinaryResolution
export interface VariationsResolution {
type: 'variations'
variants: PlatformAssetResolution[]
}
export type Resolution = AtomicResolution | VariationsResolution
export interface ResolveResult {
id: PkgResolutionId