mirror of
https://github.com/pnpm/pnpm.git
synced 2025-12-24 07:38:12 -05:00
feat: add support for the jsr: protocol (#9358)
close #8941 --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
This commit is contained in:
5
.changeset/ripe-pianos-stand.md
Normal file
5
.changeset/ripe-pianos-stand.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@pnpm/resolving.jsr-specifier-parser": major
|
||||
---
|
||||
|
||||
Initial release.
|
||||
54
.changeset/silly-ravens-sink.md
Normal file
54
.changeset/silly-ravens-sink.md
Normal file
@@ -0,0 +1,54 @@
|
||||
---
|
||||
"@pnpm/config": minor
|
||||
"@pnpm/normalize-registries": minor
|
||||
"@pnpm/npm-resolver": minor
|
||||
"@pnpm/package-requester": minor
|
||||
"@pnpm/resolve-dependencies": minor
|
||||
"@pnpm/exportable-manifest": minor
|
||||
"@pnpm/default-resolver": minor
|
||||
"@pnpm/resolver-base": minor
|
||||
"@pnpm/store-controller-types": minor
|
||||
"pnpm": minor
|
||||
---
|
||||
|
||||
**Added support for installing JSR packages.** You can now install JSR packages using the following syntax:
|
||||
|
||||
```
|
||||
pnpm add jsr:<pkg_name>
|
||||
```
|
||||
|
||||
or with a version range:
|
||||
|
||||
```
|
||||
pnpm add jsr:<pkg_name>@<range>
|
||||
```
|
||||
|
||||
For example, running:
|
||||
|
||||
```
|
||||
pnpm add jsr:@foo/bar
|
||||
```
|
||||
|
||||
will add the following entry to your `package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@foo/bar": "jsr:^0.1.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When publishing, this entry will be transformed into a format compatible with npm, older versions of Yarn, and previous pnpm versions:
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@foo/bar": "npm:@jsr/foo__bar@^0.1.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Related issue: [#8941](https://github.com/pnpm/pnpm/issues/8941).
|
||||
|
||||
Note: The `@jsr` scope defaults to <https://npm.jsr.io/> if the `@jsr:registry` setting is not defined.
|
||||
@@ -242,7 +242,10 @@ export async function getConfig (opts: {
|
||||
? pnpmConfig.rawLocalConfig['user-agent']
|
||||
: `${packageManager.name}/${packageManager.version} npm/? node/${process.version} ${process.platform} ${process.arch}`
|
||||
pnpmConfig.rawConfig = Object.assign.apply(Object, [
|
||||
{ registry: 'https://registry.npmjs.org/' },
|
||||
{
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
'@jsr:registry': 'https://npm.jsr.io/',
|
||||
},
|
||||
...[...npmConfig.list].reverse(),
|
||||
cliOptions,
|
||||
{ 'user-agent': pnpmConfig.userAgent },
|
||||
|
||||
@@ -204,6 +204,7 @@ test('registries of scoped packages are read and normalized', async () => {
|
||||
|
||||
expect(config.registries).toStrictEqual({
|
||||
default: 'https://default.com/',
|
||||
'@jsr': 'https://npm.jsr.io/',
|
||||
'@foo': 'https://foo.com/',
|
||||
'@bar': 'https://bar.com/',
|
||||
'@qar': 'https://qar.com/qar',
|
||||
@@ -227,6 +228,7 @@ test('registries in current directory\'s .npmrc have bigger priority then global
|
||||
|
||||
expect(config.registries).toStrictEqual({
|
||||
default: 'https://pnpm.io/',
|
||||
'@jsr': 'https://npm.jsr.io/',
|
||||
'@foo': 'https://foo.com/',
|
||||
'@bar': 'https://bar.com/',
|
||||
'@qar': 'https://qar.com/qar',
|
||||
|
||||
@@ -2,8 +2,9 @@ import { type Registries } from '@pnpm/types'
|
||||
import normalizeRegistryUrl from 'normalize-registry-url'
|
||||
import mapValues from 'ramda/src/map'
|
||||
|
||||
export const DEFAULT_REGISTRIES = {
|
||||
export const DEFAULT_REGISTRIES: Registries = {
|
||||
default: 'https://registry.npmjs.org/',
|
||||
'@jsr': 'https://npm.jsr.io/',
|
||||
}
|
||||
|
||||
export function normalizeRegistries (registries?: Record<string, string>): Registries {
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
"logstream",
|
||||
"longlink",
|
||||
"longpaths",
|
||||
"luca",
|
||||
"martensson",
|
||||
"maxtimeout",
|
||||
"mdast",
|
||||
|
||||
@@ -162,6 +162,7 @@ async function resolveAndFetch (
|
||||
let latest: string | undefined
|
||||
let manifest: DependencyManifest | undefined
|
||||
let specifier: string | undefined
|
||||
let alias: string | undefined
|
||||
let resolution = options.currentPkg?.resolution as Resolution
|
||||
let pkgId = options.currentPkg?.id
|
||||
const skipResolution = resolution && !options.update
|
||||
@@ -209,6 +210,7 @@ async function resolveAndFetch (
|
||||
resolution = resolveResult.resolution
|
||||
pkgId = resolveResult.id
|
||||
specifier = resolveResult.specifier
|
||||
alias = resolveResult.alias
|
||||
}
|
||||
|
||||
const id = pkgId!
|
||||
@@ -226,6 +228,7 @@ async function resolveAndFetch (
|
||||
resolvedVia,
|
||||
updated,
|
||||
specifier,
|
||||
alias,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -259,6 +262,7 @@ async function resolveAndFetch (
|
||||
resolvedVia,
|
||||
updated,
|
||||
publishedAt,
|
||||
alias,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -295,6 +299,7 @@ async function resolveAndFetch (
|
||||
resolvedVia,
|
||||
updated,
|
||||
publishedAt,
|
||||
alias,
|
||||
},
|
||||
fetching: fetchResult.fetching,
|
||||
filesIndexFile: fetchResult.filesIndexFile,
|
||||
|
||||
218
pkg-manager/plugin-commands-installation/test/addJsr.ts
Normal file
218
pkg-manager/plugin-commands-installation/test/addJsr.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import path from 'path'
|
||||
import { type LockfileFile } from '@pnpm/lockfile.types'
|
||||
import { add } from '@pnpm/plugin-commands-installation'
|
||||
import { prepare } from '@pnpm/prepare'
|
||||
import { type ProjectManifest } from '@pnpm/types'
|
||||
import { sync as loadJsonFile } from 'load-json-file'
|
||||
import { DEFAULT_OPTS } from './utils'
|
||||
|
||||
// This must be a function because some of its values depend on CWD
|
||||
const createOptions = (jsr: string = 'https://npm.jsr.io/') => ({
|
||||
...DEFAULT_OPTS,
|
||||
rawConfig: {
|
||||
...DEFAULT_OPTS.rawConfig,
|
||||
'@jsr:registry': jsr,
|
||||
},
|
||||
registries: {
|
||||
...DEFAULT_OPTS.registries,
|
||||
'@jsr': jsr,
|
||||
},
|
||||
dir: process.cwd(),
|
||||
cacheDir: path.resolve('cache'),
|
||||
storeDir: path.resolve('store'),
|
||||
})
|
||||
|
||||
test('pnpm add jsr:@<scope>/<name>', async () => {
|
||||
const project = prepare({
|
||||
name: 'test-add-jsr',
|
||||
version: '0.0.0',
|
||||
private: true,
|
||||
})
|
||||
|
||||
await add.handler(createOptions(), ['jsr:@pnpm-e2e/foo'])
|
||||
|
||||
expect(loadJsonFile('package.json')).toMatchObject({
|
||||
dependencies: {
|
||||
'@pnpm-e2e/foo': 'jsr:^0.1.0',
|
||||
},
|
||||
} as ProjectManifest)
|
||||
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'@pnpm-e2e/foo': {
|
||||
specifier: 'jsr:^0.1.0',
|
||||
version: '@jsr/pnpm-e2e__foo@0.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
})
|
||||
|
||||
test('pnpm add jsr:@<scope>/<name>@latest', async () => {
|
||||
const project = prepare({
|
||||
name: 'test-add-jsr',
|
||||
version: '0.0.0',
|
||||
private: true,
|
||||
})
|
||||
|
||||
await add.handler(createOptions(), ['jsr:@pnpm-e2e/foo@latest'])
|
||||
|
||||
expect(loadJsonFile('package.json')).toMatchObject({
|
||||
dependencies: {
|
||||
'@pnpm-e2e/foo': 'jsr:^0.1.0',
|
||||
},
|
||||
} as ProjectManifest)
|
||||
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'@pnpm-e2e/foo': {
|
||||
specifier: 'jsr:^0.1.0',
|
||||
version: '@jsr/pnpm-e2e__foo@0.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
})
|
||||
|
||||
test('pnpm add jsr:@<scope>/<name>@<version_selector>', async () => {
|
||||
const project = prepare({
|
||||
name: 'test-add-jsr',
|
||||
version: '0.0.0',
|
||||
private: true,
|
||||
})
|
||||
|
||||
await add.handler(createOptions(), ['jsr:@pnpm-e2e/foo@0.1'])
|
||||
|
||||
expect(loadJsonFile('package.json')).toMatchObject({
|
||||
dependencies: {
|
||||
'@pnpm-e2e/foo': 'jsr:~0.1.0',
|
||||
},
|
||||
} as ProjectManifest)
|
||||
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'@pnpm-e2e/foo': {
|
||||
specifier: 'jsr:~0.1.0',
|
||||
version: '@jsr/pnpm-e2e__foo@0.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
})
|
||||
|
||||
test('pnpm add <alias>@jsr:@<scope>/<name>', async () => {
|
||||
const project = prepare({
|
||||
name: 'test-add-jsr',
|
||||
version: '0.0.0',
|
||||
private: true,
|
||||
})
|
||||
|
||||
await add.handler(createOptions(), ['foo-from-jsr@jsr:@pnpm-e2e/foo'])
|
||||
|
||||
expect(loadJsonFile('package.json')).toMatchObject({
|
||||
dependencies: {
|
||||
'foo-from-jsr': 'jsr:@pnpm-e2e/foo@^0.1.0',
|
||||
},
|
||||
} as ProjectManifest)
|
||||
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'foo-from-jsr': {
|
||||
specifier: 'jsr:@pnpm-e2e/foo@^0.1.0',
|
||||
version: '@jsr/pnpm-e2e__foo@0.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
})
|
||||
|
||||
test('pnpm add <alias>@jsr:@<scope>/<name>@<version_selector>', async () => {
|
||||
const project = prepare({
|
||||
name: 'test-add-jsr',
|
||||
version: '0.0.0',
|
||||
private: true,
|
||||
})
|
||||
|
||||
await add.handler(createOptions(), ['foo-from-jsr@jsr:@pnpm-e2e/foo@0.1'])
|
||||
|
||||
expect(loadJsonFile('package.json')).toMatchObject({
|
||||
dependencies: {
|
||||
'foo-from-jsr': 'jsr:@pnpm-e2e/foo@~0.1.0',
|
||||
},
|
||||
} as ProjectManifest)
|
||||
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'foo-from-jsr': {
|
||||
specifier: 'jsr:@pnpm-e2e/foo@~0.1.0',
|
||||
version: '@jsr/pnpm-e2e__foo@0.1.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__foo@0.1.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
})
|
||||
156
pkg-manager/plugin-commands-installation/test/update/jsr.ts
Normal file
156
pkg-manager/plugin-commands-installation/test/update/jsr.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import path from 'path'
|
||||
import { type LockfileFile } from '@pnpm/lockfile.types'
|
||||
import { install, update } from '@pnpm/plugin-commands-installation'
|
||||
import { prepare } from '@pnpm/prepare'
|
||||
import { addDistTag } from '@pnpm/registry-mock'
|
||||
import { type ProjectManifest } from '@pnpm/types'
|
||||
import { sync as loadJsonFile } from 'load-json-file'
|
||||
import { DEFAULT_OPTS } from '../utils'
|
||||
|
||||
// This must be a function because some of its values depend on CWD
|
||||
const createOptions = (jsr: string = DEFAULT_OPTS.registry) => ({
|
||||
...DEFAULT_OPTS,
|
||||
rawConfig: {
|
||||
...DEFAULT_OPTS.rawConfig,
|
||||
'@jsr:registry': jsr,
|
||||
},
|
||||
registries: {
|
||||
...DEFAULT_OPTS.registries,
|
||||
'@jsr': jsr,
|
||||
},
|
||||
dir: process.cwd(),
|
||||
cacheDir: path.resolve('cache'),
|
||||
storeDir: path.resolve('store'),
|
||||
})
|
||||
|
||||
test('jsr without alias', async () => {
|
||||
await addDistTag({ package: '@jsr/pnpm-e2e__bar', version: '2.0.0', distTag: 'latest' })
|
||||
|
||||
const project = prepare({
|
||||
dependencies: {
|
||||
'@pnpm-e2e/bar': 'jsr:1.0.0',
|
||||
},
|
||||
})
|
||||
|
||||
await install.handler(createOptions())
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'@pnpm-e2e/bar': {
|
||||
specifier: 'jsr:1.0.0',
|
||||
version: '@jsr/pnpm-e2e__bar@1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__bar@1.0.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__bar@1.0.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
|
||||
await update.handler({
|
||||
...createOptions(),
|
||||
latest: true,
|
||||
})
|
||||
expect(loadJsonFile('package.json')).toMatchObject({
|
||||
dependencies: {
|
||||
'@pnpm-e2e/bar': 'jsr:2.0.0',
|
||||
},
|
||||
} as Partial<ProjectManifest>)
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'@pnpm-e2e/bar': {
|
||||
specifier: 'jsr:2.0.0',
|
||||
version: '@jsr/pnpm-e2e__bar@2.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__bar@2.0.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__bar@2.0.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
})
|
||||
|
||||
test('jsr with alias', async () => {
|
||||
await addDistTag({ package: '@jsr/pnpm-e2e__bar', version: '2.0.0', distTag: 'latest' })
|
||||
|
||||
const project = prepare({
|
||||
dependencies: {
|
||||
'bar-from-jsr': 'jsr:@pnpm-e2e/bar@1.0.0',
|
||||
},
|
||||
})
|
||||
|
||||
await install.handler(createOptions())
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'bar-from-jsr': {
|
||||
specifier: 'jsr:@pnpm-e2e/bar@1.0.0',
|
||||
version: '@jsr/pnpm-e2e__bar@1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__bar@1.0.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__bar@1.0.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
|
||||
await update.handler({
|
||||
...createOptions(),
|
||||
latest: true,
|
||||
})
|
||||
expect(loadJsonFile('package.json')).toMatchObject({
|
||||
dependencies: {
|
||||
'bar-from-jsr': 'jsr:@pnpm-e2e/bar@2.0.0',
|
||||
},
|
||||
} as Partial<ProjectManifest>)
|
||||
expect(project.readLockfile()).toMatchObject({
|
||||
importers: {
|
||||
'.': {
|
||||
dependencies: {
|
||||
'bar-from-jsr': {
|
||||
specifier: 'jsr:@pnpm-e2e/bar@2.0.0',
|
||||
version: '@jsr/pnpm-e2e__bar@2.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
packages: {
|
||||
'@jsr/pnpm-e2e__bar@2.0.0': {
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
},
|
||||
},
|
||||
},
|
||||
snapshots: {
|
||||
'@jsr/pnpm-e2e__bar@2.0.0': expect.any(Object),
|
||||
},
|
||||
} as Partial<LockfileFile>)
|
||||
})
|
||||
@@ -1351,7 +1351,7 @@ async function resolveDependency (
|
||||
throw new PnpmError('MISSING_PACKAGE_JSON', `Can't install ${wantedDependency.pref}: Missing package.json file`)
|
||||
}
|
||||
return {
|
||||
alias: wantedDependency.alias || pkgResponse.body.manifest.name || path.basename(pkgResponse.body.resolution.directory),
|
||||
alias: wantedDependency.alias ?? pkgResponse.body.alias ?? pkgResponse.body.manifest.name ?? path.basename(pkgResponse.body.resolution.directory),
|
||||
dev: wantedDependency.dev,
|
||||
isLinkedDependency: true,
|
||||
name: pkgResponse.body.manifest.name,
|
||||
@@ -1527,7 +1527,7 @@ async function resolveDependency (
|
||||
ctx.dependenciesTree.get(nodeId)!.depth = Math.min(ctx.dependenciesTree.get(nodeId)!.depth, options.currentDepth)
|
||||
} else {
|
||||
ctx.pendingNodes.push({
|
||||
alias: wantedDependency.alias || pkg.name,
|
||||
alias: wantedDependency.alias ?? pkgResponse.body.alias ?? pkg.name,
|
||||
depth: options.currentDepth,
|
||||
parentIds: options.parentIds,
|
||||
installable,
|
||||
@@ -1566,7 +1566,7 @@ async function resolveDependency (
|
||||
}
|
||||
}
|
||||
return {
|
||||
alias: wantedDependency.alias || pkg.name,
|
||||
alias: wantedDependency.alias ?? pkgResponse.body.alias ?? pkg.name,
|
||||
depIsLinked,
|
||||
resolvedVia: pkgResponse.body.resolvedVia,
|
||||
isNew,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"@pnpm/catalogs.resolver": "workspace:*",
|
||||
"@pnpm/error": "workspace:*",
|
||||
"@pnpm/read-project-manifest": "workspace:*",
|
||||
"@pnpm/resolving.jsr-specifier-parser": "workspace:*",
|
||||
"@pnpm/types": "workspace:*",
|
||||
"p-map-values": "catalog:",
|
||||
"ramda": "catalog:"
|
||||
|
||||
@@ -2,6 +2,7 @@ import path from 'path'
|
||||
import { type CatalogResolver, resolveFromCatalog } from '@pnpm/catalogs.resolver'
|
||||
import { type Catalogs } from '@pnpm/catalogs.types'
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
import { parseJsrSpecifier } from '@pnpm/resolving.jsr-specifier-parser'
|
||||
import { tryReadProjectManifest } from '@pnpm/read-project-manifest'
|
||||
import { type Dependencies, type ProjectManifest } from '@pnpm/types'
|
||||
import omit from 'ramda/src/omit'
|
||||
@@ -36,7 +37,7 @@ export async function createExportableManifest (
|
||||
const catalogResolver = resolveFromCatalog.bind(null, opts.catalogs)
|
||||
const replaceCatalogProtocol = resolveCatalogProtocol.bind(null, catalogResolver)
|
||||
|
||||
const convertDependencyForPublish = combineConverters(replaceWorkspaceProtocol, replaceCatalogProtocol)
|
||||
const convertDependencyForPublish = combineConverters(replaceWorkspaceProtocol, replaceCatalogProtocol, replaceJsrProtocol)
|
||||
await Promise.all((['dependencies', 'devDependencies', 'optionalDependencies'] as const).map(async (depsField) => {
|
||||
const deps = await makePublishDependencies(dir, originalManifest[depsField], {
|
||||
modulesDir: opts?.modulesDir,
|
||||
@@ -49,7 +50,7 @@ export async function createExportableManifest (
|
||||
|
||||
const peerDependencies = originalManifest.peerDependencies
|
||||
if (peerDependencies) {
|
||||
const convertPeersForPublish = combineConverters(replaceWorkspaceProtocolPeerDependency, replaceCatalogProtocol)
|
||||
const convertPeersForPublish = combineConverters(replaceWorkspaceProtocolPeerDependency, replaceCatalogProtocol, replaceJsrProtocol)
|
||||
publishManifest.peerDependencies = await makePublishDependencies(dir, peerDependencies, {
|
||||
modulesDir: opts?.modulesDir,
|
||||
convertDependencyForPublish: convertPeersForPublish,
|
||||
@@ -178,3 +179,19 @@ async function replaceWorkspaceProtocolPeerDependency (depName: string, depSpec:
|
||||
|
||||
return depSpec.replace('workspace:', '')
|
||||
}
|
||||
|
||||
async function replaceJsrProtocol (depName: string, depSpec: string): Promise<string> {
|
||||
const spec = parseJsrSpecifier(depSpec, depName)
|
||||
if (spec == null) {
|
||||
return depSpec
|
||||
}
|
||||
return createNpmAliasedSpecifier(spec.npmPkgName, spec.versionSelector)
|
||||
}
|
||||
|
||||
function createNpmAliasedSpecifier (npmPkgName: string, versionSelector?: string): string {
|
||||
const npmPkgSpecifier = `npm:${npmPkgName}`
|
||||
if (!versionSelector) {
|
||||
return npmPkgSpecifier
|
||||
}
|
||||
return `${npmPkgSpecifier}@${versionSelector}`
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ test('workspace deps are replaced', async () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('catalog deps are replace', async () => {
|
||||
test('catalog deps are replaced', async () => {
|
||||
const catalogProtocolPackageManifest: ProjectManifest = {
|
||||
name: 'catalog-protocol-package',
|
||||
version: '1.0.0',
|
||||
@@ -218,3 +218,37 @@ test('catalog deps are replace', async () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('jsr deps are replaced', async () => {
|
||||
const jsrProtocolPackageManifest = {
|
||||
name: 'jsr-protocol-manifest',
|
||||
version: '0.0.0',
|
||||
dependencies: {
|
||||
'@foo/bar': 'jsr:^1.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
baz: 'jsr:@foo/baz@3.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
qux: 'jsr:@foo/qux',
|
||||
},
|
||||
} satisfies ProjectManifest
|
||||
|
||||
preparePackages([jsrProtocolPackageManifest])
|
||||
|
||||
process.chdir(jsrProtocolPackageManifest.name)
|
||||
|
||||
expect(await createExportableManifest(process.cwd(), jsrProtocolPackageManifest, { catalogs: {} })).toStrictEqual({
|
||||
name: 'jsr-protocol-manifest',
|
||||
version: '0.0.0',
|
||||
dependencies: {
|
||||
'@foo/bar': 'npm:@jsr/foo__bar@^1.0.0',
|
||||
},
|
||||
optionalDependencies: {
|
||||
baz: 'npm:@jsr/foo__baz@3.0',
|
||||
},
|
||||
peerDependencies: {
|
||||
qux: 'npm:@jsr/foo__qux',
|
||||
},
|
||||
} as Partial<typeof jsrProtocolPackageManifest>)
|
||||
})
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
{
|
||||
"path": "../../packages/types"
|
||||
},
|
||||
{
|
||||
"path": "../../resolving/jsr-specifier-parser"
|
||||
},
|
||||
{
|
||||
"path": "../read-project-manifest"
|
||||
}
|
||||
|
||||
69
pnpm-lock.yaml
generated
69
pnpm-lock.yaml
generated
@@ -55,8 +55,8 @@ catalogs:
|
||||
specifier: 0.0.1
|
||||
version: 0.0.1
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 4.3.0
|
||||
version: 4.3.0
|
||||
specifier: 4.4.0
|
||||
version: 4.4.0
|
||||
'@pnpm/semver-diff':
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0
|
||||
@@ -865,7 +865,7 @@ importers:
|
||||
version: link:../../pkg-manager/modules-yaml
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
@@ -899,7 +899,7 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/store.cafs':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/cafs
|
||||
@@ -974,7 +974,7 @@ importers:
|
||||
dependencies:
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/worker':
|
||||
specifier: workspace:*
|
||||
version: link:../../worker
|
||||
@@ -1160,7 +1160,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@types/ramda':
|
||||
specifier: 'catalog:'
|
||||
version: 0.29.12
|
||||
@@ -1645,7 +1645,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/testing.temp-store':
|
||||
specifier: workspace:*
|
||||
version: link:../../testing/temp-store
|
||||
@@ -2281,7 +2281,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
@@ -2567,7 +2567,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -2715,7 +2715,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-ipc-server':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-ipc-server
|
||||
@@ -4410,7 +4410,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -4697,7 +4697,7 @@ importers:
|
||||
version: link:../../pkg-manifest/read-package-json
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/store-path':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/store-path
|
||||
@@ -4970,7 +4970,7 @@ importers:
|
||||
version: link:../read-projects-context
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/store-path':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/store-path
|
||||
@@ -5333,7 +5333,7 @@ importers:
|
||||
version: 'link:'
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -5556,7 +5556,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -5852,6 +5852,9 @@ importers:
|
||||
'@pnpm/read-project-manifest':
|
||||
specifier: workspace:*
|
||||
version: link:../read-project-manifest
|
||||
'@pnpm/resolving.jsr-specifier-parser':
|
||||
specifier: workspace:*
|
||||
version: link:../../resolving/jsr-specifier-parser
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
@@ -6151,7 +6154,7 @@ importers:
|
||||
version: link:../pkg-manifest/read-project-manifest
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/run-npm':
|
||||
specifier: workspace:*
|
||||
version: link:../exec/run-npm
|
||||
@@ -6464,7 +6467,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -6588,7 +6591,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-ipc-server':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-ipc-server
|
||||
@@ -6706,6 +6709,16 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 1.0.2
|
||||
|
||||
resolving/jsr-specifier-parser:
|
||||
dependencies:
|
||||
'@pnpm/error':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/error
|
||||
devDependencies:
|
||||
'@pnpm/resolving.jsr-specifier-parser':
|
||||
specifier: workspace:*
|
||||
version: 'link:'
|
||||
|
||||
resolving/local-resolver:
|
||||
dependencies:
|
||||
'@pnpm/crypto.hash':
|
||||
@@ -6766,6 +6779,9 @@ importers:
|
||||
'@pnpm/resolver-base':
|
||||
specifier: workspace:*
|
||||
version: link:../resolver-base
|
||||
'@pnpm/resolving.jsr-specifier-parser':
|
||||
specifier: workspace:*
|
||||
version: link:../jsr-specifier-parser
|
||||
'@pnpm/types':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/types
|
||||
@@ -7187,7 +7203,7 @@ importers:
|
||||
version: link:../../pkg-manifest/read-package-json
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -7251,7 +7267,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/workspace.filter-packages-from-dir':
|
||||
specifier: workspace:*
|
||||
version: link:../../workspace/filter-packages-from-dir
|
||||
@@ -7336,7 +7352,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/test-fixtures':
|
||||
specifier: workspace:*
|
||||
version: link:../../__utils__/test-fixtures
|
||||
@@ -7690,7 +7706,7 @@ importers:
|
||||
version: link:../../__utils__/prepare
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@types/archy':
|
||||
specifier: 'catalog:'
|
||||
version: 0.0.33
|
||||
@@ -7944,7 +7960,7 @@ importers:
|
||||
version: link:../../store/package-store
|
||||
'@pnpm/registry-mock':
|
||||
specifier: 'catalog:'
|
||||
version: 4.3.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
version: 4.4.0(encoding@0.1.13)(typanion@3.14.0)
|
||||
'@pnpm/store-controller-types':
|
||||
specifier: workspace:*
|
||||
version: link:../../store/store-controller-types
|
||||
@@ -9539,8 +9555,8 @@ packages:
|
||||
resolution: {integrity: sha512-UY5ZFl8jTgWpPMp3qwVt1z455gDLGh4aAna7ufqsJP9qhI6lr9scFpnEamjpA51Y3MJMBtnML8KATmH6RY+NHQ==}
|
||||
engines: {node: '>=18.12'}
|
||||
|
||||
'@pnpm/registry-mock@4.3.0':
|
||||
resolution: {integrity: sha512-u2SG2b/yVgeA+Nt+Wvzl6dOFBUx/l6/zCq3vX2VbWOgibTqiJwMY7ks9aXxldyE3Hk8ZZLX5FYcYIKVrC6r1/g==}
|
||||
'@pnpm/registry-mock@4.4.0':
|
||||
resolution: {integrity: sha512-OOHciXVfvqt4U70C0w+GyYnT3KrIvLG/1O1OE9fZQRji+/XdhiJd68kfhLhnTvlgi++r3mO1URlKldvi2zV/Yw==}
|
||||
engines: {node: '>=18.12'}
|
||||
hasBin: true
|
||||
|
||||
@@ -14788,7 +14804,6 @@ packages:
|
||||
verdaccio@5.20.1:
|
||||
resolution: {integrity: sha512-zKQXYubQOfl2w09gO9BR7U9ZZkFPPby8tvV+na86/2vGZnY79kNSVnSbK8CM1bpJHTCQ80AGsmIGovg2FgXhdQ==}
|
||||
engines: {node: '>=12.18'}
|
||||
deprecated: this version is deprecated, please migrate to 6.x versions
|
||||
hasBin: true
|
||||
|
||||
verror@1.10.0:
|
||||
@@ -16547,7 +16562,7 @@ snapshots:
|
||||
read-yaml-file: 2.1.0
|
||||
strip-bom: 4.0.0
|
||||
|
||||
'@pnpm/registry-mock@4.3.0(encoding@0.1.13)(typanion@3.14.0)':
|
||||
'@pnpm/registry-mock@4.4.0(encoding@0.1.13)(typanion@3.14.0)':
|
||||
dependencies:
|
||||
anonymous-npm-registry-client: 0.3.2
|
||||
execa: 5.1.1
|
||||
|
||||
@@ -74,7 +74,7 @@ catalog:
|
||||
"@pnpm/npm-package-arg": ^1.0.0
|
||||
"@pnpm/os.env.path-extender": ^2.0.2
|
||||
"@pnpm/patch-package": 0.0.1
|
||||
"@pnpm/registry-mock": 4.3.0
|
||||
"@pnpm/registry-mock": 4.4.0
|
||||
"@pnpm/semver-diff": ^1.1.0
|
||||
"@pnpm/tabtab": ^0.5.4
|
||||
"@pnpm/util.lex-comparator": 3.0.1
|
||||
|
||||
@@ -646,7 +646,7 @@ test('preResolution hook', async () => {
|
||||
expect(ctx.existsCurrentLockfile).toBe(false)
|
||||
expect(ctx.existsNonEmptyWantedLockfile).toBe(false)
|
||||
|
||||
expect(ctx.registries).toEqual({
|
||||
expect(ctx.registries).toMatchObject({
|
||||
default: `http://localhost:${REGISTRY_MOCK_PORT}/`,
|
||||
'@foo': 'https://foo.com/',
|
||||
})
|
||||
|
||||
@@ -24,11 +24,12 @@ export function createResolver (
|
||||
getAuthHeader: GetAuthHeader,
|
||||
pnpmOpts: ResolverFactoryOptions
|
||||
): { resolve: ResolveFunction, clearCache: () => void } {
|
||||
const { resolveFromNpm, clearCache } = createNpmResolver(fetchFromRegistry, getAuthHeader, pnpmOpts)
|
||||
const { resolveFromNpm, resolveFromJsr, clearCache } = createNpmResolver(fetchFromRegistry, getAuthHeader, pnpmOpts)
|
||||
const resolveFromGit = createGitResolver(pnpmOpts)
|
||||
return {
|
||||
resolve: async (wantedDependency, opts) => {
|
||||
const resolution = await resolveFromNpm(wantedDependency, opts as ResolveFromNpmOptions) ??
|
||||
await resolveFromJsr(wantedDependency, opts as ResolveFromNpmOptions) ??
|
||||
(wantedDependency.pref && (
|
||||
await resolveFromTarball(fetchFromRegistry, wantedDependency as { pref: string }) ??
|
||||
await resolveFromGit(wantedDependency as { pref: string }) ??
|
||||
|
||||
17
resolving/jsr-specifier-parser/README.md
Normal file
17
resolving/jsr-specifier-parser/README.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# @pnpm/resolving.jsr-specifier-parser
|
||||
|
||||
> Parser of jsr specifiers
|
||||
|
||||
<!--@shields('npm')-->
|
||||
[](https://www.npmjs.com/package/@pnpm/resolving.jsr-specifier-parser)
|
||||
<!--/@-->
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @pnpm/resolving.jsr-specifier-parser
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
45
resolving/jsr-specifier-parser/package.json
Normal file
45
resolving/jsr-specifier-parser/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@pnpm/resolving.jsr-specifier-parser",
|
||||
"version": "1000.0.0-0",
|
||||
"description": "Parser of jsr specifiers",
|
||||
"keywords": [
|
||||
"pnpm",
|
||||
"pnpm10",
|
||||
"jsr"
|
||||
],
|
||||
"license": "MIT",
|
||||
"funding": "https://opencollective.com/pnpm",
|
||||
"repository": "https://github.com/pnpm/pnpm/blob/main/resolving/jsr-specifier-parser",
|
||||
"homepage": "https://github.com/pnpm/pnpm/blob/main/resolving/jsr-specifier-parser#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pnpm/pnpm/issues"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"exports": {
|
||||
".": "./lib/index.js"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"!*.map"
|
||||
],
|
||||
"scripts": {
|
||||
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"_test": "jest",
|
||||
"test": "pnpm run compile && pnpm run _test",
|
||||
"prepublishOnly": "pnpm run compile",
|
||||
"compile": "tsc --build && pnpm run lint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pnpm/error": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pnpm/resolving.jsr-specifier-parser": "workspace:*"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@pnpm/jest-config"
|
||||
}
|
||||
}
|
||||
64
resolving/jsr-specifier-parser/src/index.ts
Normal file
64
resolving/jsr-specifier-parser/src/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { PnpmError } from '@pnpm/error'
|
||||
|
||||
export interface JsrSpec {
|
||||
jsrPkgName: string
|
||||
npmPkgName: string
|
||||
// A versionSelector may be a semver range (e.g. ^1.0.0), exact version (e.g. 2.3.4), or a dist-tag (e.g. "latest").
|
||||
versionSelector?: string
|
||||
}
|
||||
|
||||
export function parseJsrSpecifier (rawSpecifier: string, alias?: string): JsrSpec | null {
|
||||
if (!rawSpecifier.startsWith('jsr:')) return null
|
||||
|
||||
rawSpecifier = rawSpecifier.substring('jsr:'.length)
|
||||
|
||||
// syntax: jsr:@<scope>/<name>[@<version_selector>]
|
||||
if (rawSpecifier.startsWith('@')) {
|
||||
const index = rawSpecifier.lastIndexOf('@')
|
||||
|
||||
// syntax: jsr:@<scope>/<name>
|
||||
if (index === 0) {
|
||||
return {
|
||||
jsrPkgName: rawSpecifier,
|
||||
npmPkgName: jsrToNpmPackageName(rawSpecifier),
|
||||
}
|
||||
}
|
||||
|
||||
// syntax: jsr:@<scope>/<name>@<version_selector>
|
||||
const jsrPkgName = rawSpecifier.substring(0, index)
|
||||
return {
|
||||
jsrPkgName,
|
||||
npmPkgName: jsrToNpmPackageName(jsrPkgName),
|
||||
versionSelector: rawSpecifier.substring(index + '@'.length),
|
||||
}
|
||||
}
|
||||
|
||||
// syntax: jsr:<name>@<version_selector> (invalid)
|
||||
if (rawSpecifier.includes('@')) {
|
||||
throw new PnpmError('MISSING_JSR_PACKAGE_SCOPE', 'Package names from JSR must have a scope')
|
||||
}
|
||||
|
||||
if (!alias) {
|
||||
throw new PnpmError('INVALID_JSR_SPECIFIER', `JSR specifier '${rawSpecifier}' is missing a package name`)
|
||||
}
|
||||
|
||||
// syntax: jsr:<spec>
|
||||
return {
|
||||
versionSelector: rawSpecifier,
|
||||
jsrPkgName: alias,
|
||||
npmPkgName: jsrToNpmPackageName(alias),
|
||||
}
|
||||
}
|
||||
|
||||
function jsrToNpmPackageName (jsrPkgName: string): string {
|
||||
if (!jsrPkgName.startsWith('@')) {
|
||||
throw new PnpmError('MISSING_JSR_PACKAGE_SCOPE', 'Package names from JSR must have a scope')
|
||||
}
|
||||
const sepIndex = jsrPkgName.indexOf('/')
|
||||
if (sepIndex === -1) {
|
||||
throw new PnpmError('INVALID_JSR_PACKAGE_NAME', `The package name '${jsrPkgName}' is invalid`)
|
||||
}
|
||||
const scope = jsrPkgName.substring(0, sepIndex)
|
||||
const name = jsrPkgName.substring(sepIndex + '/'.length)
|
||||
return `@jsr/${scope.substring(1)}__${name}`
|
||||
}
|
||||
45
resolving/jsr-specifier-parser/test/parse.test.ts
Normal file
45
resolving/jsr-specifier-parser/test/parse.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { parseJsrSpecifier, type JsrSpec } from '@pnpm/resolving.jsr-specifier-parser'
|
||||
|
||||
describe('parseJsrSpecifier', () => {
|
||||
test('skips on non-jsr specifiers', () => {
|
||||
expect(parseJsrSpecifier('^1.0.0')).toBeNull()
|
||||
expect(parseJsrSpecifier('1.0.0')).toBeNull()
|
||||
expect(parseJsrSpecifier('latest')).toBeNull()
|
||||
expect(parseJsrSpecifier('npm:foo')).toBeNull()
|
||||
expect(parseJsrSpecifier('npm:@foo/bar')).toBeNull()
|
||||
expect(parseJsrSpecifier('npm:@jsr/foo__bar')).toBeNull()
|
||||
expect(parseJsrSpecifier('catalog:')).toBeNull()
|
||||
expect(parseJsrSpecifier('workspace:*')).toBeNull()
|
||||
})
|
||||
|
||||
test('succeeds on jsr specifiers that only specify versions/ranges/tags (jsr:<version_selector>)', () => {
|
||||
expect(parseJsrSpecifier('jsr:^1.0.0', '@foo/bar')).toStrictEqual({ versionSelector: '^1.0.0', jsrPkgName: '@foo/bar', npmPkgName: '@jsr/foo__bar' } as JsrSpec)
|
||||
expect(parseJsrSpecifier('jsr:1.0.0', '@foo/bar')).toStrictEqual({ versionSelector: '1.0.0', jsrPkgName: '@foo/bar', npmPkgName: '@jsr/foo__bar' } as JsrSpec)
|
||||
expect(parseJsrSpecifier('jsr:latest', '@foo/bar')).toStrictEqual({ versionSelector: 'latest', jsrPkgName: '@foo/bar', npmPkgName: '@jsr/foo__bar' } as JsrSpec)
|
||||
})
|
||||
|
||||
test('succeeds on jsr specifiers that only specify scope and name (jsr:@<scope>/<name>)', () => {
|
||||
expect(parseJsrSpecifier('jsr:@foo/bar')).toStrictEqual({ jsrPkgName: '@foo/bar', npmPkgName: '@jsr/foo__bar' } as JsrSpec)
|
||||
})
|
||||
|
||||
test('succeeds on jsr specifiers that specify scopes, names, and versions/ranges/tags (jsr:@<scope>/<name>@<version_selector>)', () => {
|
||||
expect(parseJsrSpecifier('jsr:@foo/bar@^1.0.0')).toStrictEqual({ jsrPkgName: '@foo/bar', npmPkgName: '@jsr/foo__bar', versionSelector: '^1.0.0' } as JsrSpec)
|
||||
expect(parseJsrSpecifier('jsr:@foo/bar@1.0.0')).toStrictEqual({ jsrPkgName: '@foo/bar', npmPkgName: '@jsr/foo__bar', versionSelector: '1.0.0' } as JsrSpec)
|
||||
expect(parseJsrSpecifier('jsr:@foo/bar@latest')).toStrictEqual({ jsrPkgName: '@foo/bar', npmPkgName: '@jsr/foo__bar', versionSelector: 'latest' } as JsrSpec)
|
||||
})
|
||||
|
||||
test('errors on jsr specifiers that contain names without scopes', () => {
|
||||
expect(() => parseJsrSpecifier('jsr:foo@^1.0.0')).toThrow(expect.objectContaining({
|
||||
code: 'ERR_PNPM_MISSING_JSR_PACKAGE_SCOPE',
|
||||
}))
|
||||
})
|
||||
|
||||
test('errors on jsr specifiers that contain scopes without names', () => {
|
||||
expect(() => parseJsrSpecifier('jsr:@foo@^1.0.0')).toThrow(expect.objectContaining({
|
||||
code: 'ERR_PNPM_INVALID_JSR_PACKAGE_NAME',
|
||||
}))
|
||||
expect(() => parseJsrSpecifier('jsr:@foo')).toThrow(expect.objectContaining({
|
||||
code: 'ERR_PNPM_INVALID_JSR_PACKAGE_NAME',
|
||||
}))
|
||||
})
|
||||
})
|
||||
17
resolving/jsr-specifier-parser/test/tsconfig.json
Normal file
17
resolving/jsr-specifier-parser/test/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": false,
|
||||
"outDir": "../test.lib",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": ".."
|
||||
}
|
||||
]
|
||||
}
|
||||
16
resolving/jsr-specifier-parser/tsconfig.json
Normal file
16
resolving/jsr-specifier-parser/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "@pnpm/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../packages/error"
|
||||
}
|
||||
]
|
||||
}
|
||||
8
resolving/jsr-specifier-parser/tsconfig.lint.json
Normal file
8
resolving/jsr-specifier-parser/tsconfig.lint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"test/**/*.ts",
|
||||
"../../__typings__/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -41,6 +41,7 @@
|
||||
"@pnpm/pick-registry-for-package": "workspace:*",
|
||||
"@pnpm/resolve-workspace-range": "workspace:*",
|
||||
"@pnpm/resolver-base": "workspace:*",
|
||||
"@pnpm/resolving.jsr-specifier-parser": "workspace:*",
|
||||
"@pnpm/types": "workspace:*",
|
||||
"@pnpm/workspace.spec-parser": "workspace:*",
|
||||
"@zkochan/retry": "catalog:",
|
||||
|
||||
@@ -34,7 +34,9 @@ import {
|
||||
pickPackage,
|
||||
} from './pickPackage'
|
||||
import {
|
||||
parseJsrSpecifierToRegistryPackageSpec,
|
||||
parsePref,
|
||||
type JsrRegistryPackageSpec,
|
||||
type RegistryPackageSpec,
|
||||
} from './parsePref'
|
||||
import { fromRegistry, RegistryResponseError } from './fetch'
|
||||
@@ -79,7 +81,7 @@ export function createNpmResolver (
|
||||
fetchFromRegistry: FetchFromRegistry,
|
||||
getAuthHeader: GetAuthHeader,
|
||||
opts: ResolverFactoryOptions
|
||||
): { resolveFromNpm: NpmResolver, clearCache: () => void } {
|
||||
): { resolveFromNpm: NpmResolver, resolveFromJsr: NpmResolver, clearCache: () => void } {
|
||||
if (typeof opts.cacheDir !== 'string') {
|
||||
throw new TypeError('`opts.cacheDir` is required and needs to be a string')
|
||||
}
|
||||
@@ -95,27 +97,36 @@ export function createNpmResolver (
|
||||
max: 10000,
|
||||
ttl: 120 * 1000, // 2 minutes
|
||||
})
|
||||
return {
|
||||
resolveFromNpm: resolveNpm.bind(null, {
|
||||
getAuthHeaderValueByURI: getAuthHeader,
|
||||
pickPackage: pickPackage.bind(null, {
|
||||
fetch,
|
||||
filterMetadata: opts.filterMetadata,
|
||||
metaCache,
|
||||
metaDir: opts.fullMetadata ? (opts.filterMetadata ? FULL_FILTERED_META_DIR : FULL_META_DIR) : ABBREVIATED_META_DIR,
|
||||
offline: opts.offline,
|
||||
preferOffline: opts.preferOffline,
|
||||
cacheDir: opts.cacheDir,
|
||||
}),
|
||||
registries: opts.registries,
|
||||
saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
|
||||
const ctx = {
|
||||
getAuthHeaderValueByURI: getAuthHeader,
|
||||
pickPackage: pickPackage.bind(null, {
|
||||
fetch,
|
||||
filterMetadata: opts.filterMetadata,
|
||||
metaCache,
|
||||
metaDir: opts.fullMetadata ? (opts.filterMetadata ? FULL_FILTERED_META_DIR : FULL_META_DIR) : ABBREVIATED_META_DIR,
|
||||
offline: opts.offline,
|
||||
preferOffline: opts.preferOffline,
|
||||
cacheDir: opts.cacheDir,
|
||||
}),
|
||||
registries: opts.registries,
|
||||
saveWorkspaceProtocol: opts.saveWorkspaceProtocol,
|
||||
}
|
||||
return {
|
||||
resolveFromNpm: resolveNpm.bind(null, ctx),
|
||||
resolveFromJsr: resolveJsr.bind(null, ctx),
|
||||
clearCache: () => {
|
||||
metaCache.clear()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export interface ResolveFromNpmContext {
|
||||
pickPackage: (spec: RegistryPackageSpec, opts: PickPackageOptions) => ReturnType<typeof pickPackage>
|
||||
getAuthHeaderValueByURI: (registry: string) => string | undefined
|
||||
registries: Registries
|
||||
saveWorkspaceProtocol?: boolean | 'rolling'
|
||||
}
|
||||
|
||||
export type ResolveFromNpmOptions = {
|
||||
alwaysTryWorkspacePackages?: boolean
|
||||
defaultTag?: string
|
||||
@@ -138,12 +149,7 @@ export type ResolveFromNpmOptions = {
|
||||
})
|
||||
|
||||
async function resolveNpm (
|
||||
ctx: {
|
||||
pickPackage: (spec: RegistryPackageSpec, opts: PickPackageOptions) => ReturnType<typeof pickPackage>
|
||||
getAuthHeaderValueByURI: (registry: string) => string | undefined
|
||||
registries: Registries
|
||||
saveWorkspaceProtocol?: boolean | 'rolling'
|
||||
},
|
||||
ctx: ResolveFromNpmContext,
|
||||
wantedDependency: WantedDependency,
|
||||
opts: ResolveFromNpmOptions
|
||||
): Promise<ResolveResult | null> {
|
||||
@@ -287,6 +293,73 @@ async function resolveNpm (
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveJsr (
|
||||
ctx: ResolveFromNpmContext,
|
||||
wantedDependency: WantedDependency,
|
||||
opts: Omit<ResolveFromNpmOptions, 'registry'>
|
||||
): Promise<ResolveResult | null> {
|
||||
if (!wantedDependency.pref) return null
|
||||
const defaultTag = opts.defaultTag ?? 'latest'
|
||||
|
||||
const registry = ctx.registries['@jsr']! // '@jsr' is always defined
|
||||
const spec = parseJsrSpecifierToRegistryPackageSpec(wantedDependency.pref, wantedDependency.alias, defaultTag)
|
||||
if (spec == null) return null
|
||||
|
||||
const authHeaderValue = ctx.getAuthHeaderValueByURI(registry)
|
||||
const { meta, pickedPackage } = await ctx.pickPackage(spec, {
|
||||
pickLowestVersion: opts.pickLowestVersion,
|
||||
publishedBy: opts.publishedBy,
|
||||
authHeaderValue,
|
||||
dryRun: opts.dryRun === true,
|
||||
preferredVersionSelectors: opts.preferredVersions?.[spec.name],
|
||||
registry,
|
||||
updateToLatest: opts.update === 'latest',
|
||||
})
|
||||
|
||||
if (pickedPackage == null) {
|
||||
throw new NoMatchingVersionError({ wantedDependency, packageMeta: meta, registry })
|
||||
}
|
||||
|
||||
const id = `${pickedPackage.name}@${pickedPackage.version}` as PkgResolutionId
|
||||
const resolution = {
|
||||
integrity: getIntegrity(pickedPackage.dist),
|
||||
tarball: pickedPackage.dist.tarball,
|
||||
}
|
||||
return {
|
||||
id,
|
||||
latest: meta['dist-tags'].latest,
|
||||
manifest: pickedPackage,
|
||||
specifier: opts.calcSpecifier
|
||||
? calcJsrSpecifier({
|
||||
wantedDependency,
|
||||
spec,
|
||||
version: pickedPackage.version,
|
||||
defaultPinnedVersion: opts.pinnedVersion,
|
||||
})
|
||||
: undefined,
|
||||
resolution,
|
||||
resolvedVia: 'jsr-registry',
|
||||
publishedAt: meta.time?.[pickedPackage.version],
|
||||
alias: spec.jsrPkgName,
|
||||
}
|
||||
}
|
||||
|
||||
function calcJsrSpecifier ({
|
||||
wantedDependency,
|
||||
spec,
|
||||
version,
|
||||
defaultPinnedVersion,
|
||||
}: {
|
||||
wantedDependency: WantedDependency
|
||||
spec: JsrRegistryPackageSpec
|
||||
version: string
|
||||
defaultPinnedVersion?: PinnedVersion
|
||||
}): string {
|
||||
const range = calcRange(version, wantedDependency, defaultPinnedVersion)
|
||||
if (!wantedDependency.alias || spec.jsrPkgName === wantedDependency.alias) return `jsr:${range}`
|
||||
return `jsr:${spec.jsrPkgName}@${range}`
|
||||
}
|
||||
|
||||
function calcSpecifier ({
|
||||
wantedDependency,
|
||||
spec,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { parseJsrSpecifier } from '@pnpm/resolving.jsr-specifier-parser'
|
||||
import parseNpmTarballUrl from 'parse-npm-tarball-url'
|
||||
import getVersionSelectorType from 'version-selector-type'
|
||||
|
||||
@@ -49,3 +50,26 @@ export function parsePref (
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export interface JsrRegistryPackageSpec extends RegistryPackageSpec {
|
||||
jsrPkgName: string
|
||||
}
|
||||
|
||||
export function parseJsrSpecifierToRegistryPackageSpec (
|
||||
rawSpecifier: string,
|
||||
alias: string | undefined,
|
||||
defaultTag: string
|
||||
): JsrRegistryPackageSpec | null {
|
||||
const spec = parseJsrSpecifier(rawSpecifier, alias)
|
||||
if (!spec?.npmPkgName) return null
|
||||
|
||||
const selector = getVersionSelectorType(spec.versionSelector ?? defaultTag)
|
||||
if (selector == null) return null
|
||||
|
||||
return {
|
||||
fetchSpec: selector.normalized,
|
||||
name: spec.npmPkgName,
|
||||
type: selector.type,
|
||||
jsrPkgName: spec.jsrPkgName,
|
||||
}
|
||||
}
|
||||
|
||||
25
resolving/npm-resolver/test/fixtures/jsr-luca-cases.json
vendored
Normal file
25
resolving/npm-resolver/test/fixtures/jsr-luca-cases.json
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "@jsr/luca__cases",
|
||||
"description": "A collection of functions for converting strings between different cases.",
|
||||
"dist-tags": {
|
||||
"latest": "1.0.0"
|
||||
},
|
||||
"versions": {
|
||||
"1.0.0": {
|
||||
"name": "@jsr/luca__cases",
|
||||
"version": "1.0.0",
|
||||
"description": "A collection of functions for converting strings between different cases.",
|
||||
"dist": {
|
||||
"tarball": "https://npm.jsr.io/~/11/@jsr/luca__cases/1.0.0.tgz",
|
||||
"shasum": "2D36BC80F4D7FA0E2F00111925746808D4AF2DB7",
|
||||
"integrity": "sha512-h3nOT3+JVCXBsLd+st9cytZO+iqJYTTsm3rOrtZZXBgJ0nzBA5mDR9dI3k8wBDQ5AAqqJjD3UkhLxoRCEKwzgg=="
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"created": "2024-03-06T12:28:36.287Z",
|
||||
"modified": "2024-03-06T12:30:42.072Z",
|
||||
"1.0.0": "2024-03-06T12:30:12.551Z"
|
||||
}
|
||||
}
|
||||
55
resolving/npm-resolver/test/fixtures/jsr-rus-greet.json
vendored
Normal file
55
resolving/npm-resolver/test/fixtures/jsr-rus-greet.json
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "@jsr/rus__greet",
|
||||
"description": "Example library that greets you!",
|
||||
"dist-tags": {
|
||||
"latest": "0.0.3"
|
||||
},
|
||||
"versions": {
|
||||
"0.0.3": {
|
||||
"name": "@jsr/rus__greet",
|
||||
"version": "0.0.3",
|
||||
"description": "Example library that greets you!",
|
||||
"dist": {
|
||||
"tarball": "https://npm.jsr.io/~/11/@jsr/rus__greet/0.0.3.tgz",
|
||||
"shasum": "99158A6BE97ECFDDB52E236A574588897321CD32",
|
||||
"integrity": "sha512-hOYkQsOF5aMKmsjzXXuttQM/gTN57Ocjwm3UYFF+siCXKjxGusqUQBsajGdvZ/zHyTr7cZg5phXi43I3KDTd5w=="
|
||||
},
|
||||
"dependencies": {
|
||||
"@jsr/luca__cases": "1.0.0"
|
||||
}
|
||||
},
|
||||
"0.0.2": {
|
||||
"name": "@jsr/rus__greet",
|
||||
"version": "0.0.2",
|
||||
"description": "Example library that greets you!",
|
||||
"dist": {
|
||||
"tarball": "https://npm.jsr.io/~/11/@jsr/rus__greet/0.0.2.tgz",
|
||||
"shasum": "D7CD221AC674E964CCF1D8FC238D5E88760B8479",
|
||||
"integrity": "sha512-+5Z534D4erkvqWjJVnQDvb1Do/rdK4sf3kuCYt2jc1TmCSuEr9tcUD1Llyl6oFpeRxYzi3f2nt8c1z/etFD0jA=="
|
||||
},
|
||||
"dependencies": {
|
||||
"@jsr/luca__cases": "1.0.0"
|
||||
}
|
||||
},
|
||||
"0.0.1": {
|
||||
"name": "@jsr/rus__greet",
|
||||
"version": "0.0.1",
|
||||
"description": "Example library that greets you!",
|
||||
"dist": {
|
||||
"tarball": "https://npm.jsr.io/~/11/@jsr/rus__greet/0.0.1.tgz",
|
||||
"shasum": "3DF04DE78584958FF55F369496BD09CEBAA4A10F",
|
||||
"integrity": "sha512-er2E93G7KqTOE9Q9gTcCgoBT2OPlqAkGE8JaH8H1+xdVZd6DBrNRJi6TMMtEVZ4YuGIyLU2N3zwhyo29AP8R+A=="
|
||||
},
|
||||
"dependencies": {
|
||||
"@jsr/luca__cases": "1.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"created": "2024-11-16T15:31:57.112Z",
|
||||
"modified": "2024-11-16T16:24:49.810Z",
|
||||
"0.0.3": "2024-11-16T16:31:27.117Z",
|
||||
"0.0.2": "2024-11-16T16:13:24.566Z",
|
||||
"0.0.1": "2024-11-16T16:08:48.829Z"
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import loadJsonFile from 'load-json-file'
|
||||
import nock from 'nock'
|
||||
import omit from 'ramda/src/omit'
|
||||
import tempy from 'tempy'
|
||||
import { delay, retryLoadJsonFile } from './utils'
|
||||
|
||||
const f = fixtures(__dirname)
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
@@ -28,33 +29,15 @@ const jsonMeta = loadJsonFile.sync<any>(f.find('JSON.json'))
|
||||
const brokenIntegrity = loadJsonFile.sync<any>(f.find('broken-integrity.json'))
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
const registries: Registries = {
|
||||
const registries = {
|
||||
default: 'https://registry.npmjs.org/',
|
||||
}
|
||||
|
||||
const delay = async (time: number) => new Promise<void>((resolve) => setTimeout(() => {
|
||||
resolve()
|
||||
}, time))
|
||||
'@jsr': 'https://npm.jsr.io/',
|
||||
} satisfies Registries
|
||||
|
||||
const fetch = createFetchFromRegistry({})
|
||||
const getAuthHeader = () => undefined
|
||||
const createResolveFromNpm = createNpmResolver.bind(null, fetch, getAuthHeader)
|
||||
|
||||
async function retryLoadJsonFile<T> (filePath: string) {
|
||||
let retry = 0
|
||||
/* eslint-disable no-await-in-loop */
|
||||
while (true) {
|
||||
await delay(500)
|
||||
try {
|
||||
return await loadJsonFile<T>(filePath)
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
if (retry > 2) throw err
|
||||
retry++
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-await-in-loop */
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
nock.cleanAll()
|
||||
nock.disableNetConnect()
|
||||
|
||||
134
resolving/npm-resolver/test/resolveJsr.test.ts
Normal file
134
resolving/npm-resolver/test/resolveJsr.test.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import path from 'path'
|
||||
import { ABBREVIATED_META_DIR } from '@pnpm/constants'
|
||||
import { createFetchFromRegistry } from '@pnpm/fetch'
|
||||
import { createNpmResolver } from '@pnpm/npm-resolver'
|
||||
import { fixtures } from '@pnpm/test-fixtures'
|
||||
import { type Registries } from '@pnpm/types'
|
||||
import loadJsonFile from 'load-json-file'
|
||||
import nock from 'nock'
|
||||
import tempy from 'tempy'
|
||||
import { retryLoadJsonFile } from './utils'
|
||||
|
||||
const f = fixtures(__dirname)
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
const jsrRusGreetMeta = loadJsonFile.sync<any>(f.find('jsr-rus-greet.json'))
|
||||
const jsrLucaCasesMeta = loadJsonFile.sync<any>(f.find('jsr-luca-cases.json'))
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
|
||||
const registries = {
|
||||
default: 'https://registry.npmjs.org/',
|
||||
'@jsr': 'https://npm.jsr.io/',
|
||||
} satisfies Registries
|
||||
|
||||
const fetch = createFetchFromRegistry({})
|
||||
const getAuthHeader = () => undefined
|
||||
const createResolveFromNpm = createNpmResolver.bind(null, fetch, getAuthHeader)
|
||||
|
||||
afterEach(() => {
|
||||
nock.cleanAll()
|
||||
nock.disableNetConnect()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
nock.enableNetConnect()
|
||||
})
|
||||
|
||||
test('resolveFromJsr() on jsr', async () => {
|
||||
const slash = '%2F'
|
||||
nock(registries.default)
|
||||
.get(`/@jsr${slash}rus__greet`)
|
||||
.reply(404)
|
||||
.get(`/@jsr${slash}luca__cases`)
|
||||
.reply(404)
|
||||
nock(registries['@jsr'])
|
||||
.get(`/@jsr${slash}rus__greet`)
|
||||
.reply(200, jsrRusGreetMeta)
|
||||
.get(`/@jsr${slash}luca__cases`)
|
||||
.reply(200, jsrLucaCasesMeta)
|
||||
|
||||
const cacheDir = tempy.directory()
|
||||
const { resolveFromJsr } = createResolveFromNpm({
|
||||
cacheDir,
|
||||
registries,
|
||||
})
|
||||
const resolveResult = await resolveFromJsr({ alias: '@rus/greet', pref: 'jsr:0.0.3' }, { calcSpecifier: true })
|
||||
|
||||
expect(resolveResult).toMatchObject({
|
||||
resolvedVia: 'jsr-registry',
|
||||
id: '@jsr/rus__greet@0.0.3',
|
||||
latest: '0.0.3',
|
||||
manifest: {
|
||||
name: '@jsr/rus__greet',
|
||||
version: '0.0.3',
|
||||
},
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
tarball: 'https://npm.jsr.io/~/11/@jsr/rus__greet/0.0.3.tgz',
|
||||
},
|
||||
specifier: 'jsr:0.0.3',
|
||||
})
|
||||
|
||||
// The resolve function does not wait for the package meta cache file to be saved
|
||||
// so we must delay for a bit in order to read it
|
||||
const meta = await retryLoadJsonFile<any>(path.join(cacheDir, ABBREVIATED_META_DIR, 'npm.jsr.io/@jsr/rus__greet.json')) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
expect(meta).toMatchObject({
|
||||
name: expect.any(String),
|
||||
versions: expect.any(Object),
|
||||
'dist-tags': expect.any(Object),
|
||||
})
|
||||
})
|
||||
|
||||
test('resolveFromJsr() on jsr with alias renaming', async () => {
|
||||
const slash = '%2F'
|
||||
nock(registries.default)
|
||||
.get(`/@jsr${slash}rus__greet`)
|
||||
.reply(404)
|
||||
.get(`/@jsr${slash}luca__cases`)
|
||||
.reply(404)
|
||||
nock(registries['@jsr'])
|
||||
.get(`/@jsr${slash}rus__greet`)
|
||||
.reply(200, jsrRusGreetMeta)
|
||||
.get(`/@jsr${slash}luca__cases`)
|
||||
.reply(200, jsrLucaCasesMeta)
|
||||
|
||||
const cacheDir = tempy.directory()
|
||||
const { resolveFromJsr } = createResolveFromNpm({
|
||||
cacheDir,
|
||||
registries,
|
||||
})
|
||||
const resolveResult = await resolveFromJsr({ alias: 'greet', pref: 'jsr:@rus/greet@0.0.3' }, {})
|
||||
|
||||
expect(resolveResult).toMatchObject({
|
||||
resolvedVia: 'jsr-registry',
|
||||
id: '@jsr/rus__greet@0.0.3',
|
||||
latest: '0.0.3',
|
||||
manifest: {
|
||||
name: '@jsr/rus__greet',
|
||||
version: '0.0.3',
|
||||
},
|
||||
resolution: {
|
||||
integrity: expect.any(String),
|
||||
tarball: 'https://npm.jsr.io/~/11/@jsr/rus__greet/0.0.3.tgz',
|
||||
},
|
||||
})
|
||||
|
||||
// The resolve function does not wait for the package meta cache file to be saved
|
||||
// so we must delay for a bit in order to read it
|
||||
const meta = await retryLoadJsonFile<any>(path.join(cacheDir, ABBREVIATED_META_DIR, 'npm.jsr.io/@jsr/rus__greet.json')) // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
expect(meta).toMatchObject({
|
||||
name: expect.any(String),
|
||||
versions: expect.any(Object),
|
||||
'dist-tags': expect.any(Object),
|
||||
})
|
||||
})
|
||||
|
||||
test('resolveFromJsr() on jsr with packages without scope', async () => {
|
||||
const cacheDir = tempy.directory()
|
||||
const { resolveFromJsr } = createResolveFromNpm({
|
||||
cacheDir,
|
||||
registries,
|
||||
})
|
||||
await expect(resolveFromJsr({ alias: 'greet', pref: 'jsr:0.0.3' }, {})).rejects.toMatchObject({
|
||||
code: 'ERR_PNPM_MISSING_JSR_PACKAGE_SCOPE',
|
||||
})
|
||||
})
|
||||
22
resolving/npm-resolver/test/utils/index.ts
Normal file
22
resolving/npm-resolver/test/utils/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import loadJsonFile from 'load-json-file'
|
||||
|
||||
export async function retryLoadJsonFile<T> (filePath: string): Promise<T> {
|
||||
let retry = 0
|
||||
/* eslint-disable no-await-in-loop */
|
||||
while (true) {
|
||||
await delay(500)
|
||||
try {
|
||||
return await loadJsonFile<T>(filePath)
|
||||
} catch (err: any) { // eslint-disable-line
|
||||
if (retry > 2) throw err
|
||||
retry++
|
||||
}
|
||||
}
|
||||
/* eslint-enable no-await-in-loop */
|
||||
}
|
||||
|
||||
export async function delay (time: number): Promise<void> {
|
||||
return new Promise<void>((resolve) => setTimeout(() => {
|
||||
resolve()
|
||||
}, time))
|
||||
}
|
||||
@@ -11,6 +11,8 @@ test.each([
|
||||
['npm:foo@^1.0.0', 'major'],
|
||||
['npm:@foo/foo@^1.0.0', 'major'],
|
||||
['npm:@pnpm.e2e/qar@100.0.0', 'patch'],
|
||||
['jsr:@foo/foo@1.0.0', 'patch'],
|
||||
['jsr:foo@^1.0.0', 'major'],
|
||||
])('whichVersionIsPinned()', (spec, expectedResult) => {
|
||||
expect(whichVersionIsPinned(spec)).toEqual(expectedResult)
|
||||
})
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
{
|
||||
"path": "../../workspace/spec-parser"
|
||||
},
|
||||
{
|
||||
"path": "../jsr-specifier-parser"
|
||||
},
|
||||
{
|
||||
"path": "../resolver-base"
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface ResolveResult {
|
||||
resolution: Resolution
|
||||
resolvedVia: 'npm-registry' | 'git-repository' | 'local-filesystem' | 'workspace' | 'url' | string
|
||||
specifier?: string
|
||||
alias?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getPkgInfo } from '../lib/getPkgInfo'
|
||||
|
||||
export const DEFAULT_REGISTRIES = {
|
||||
default: 'https://registry.npmjs.org/',
|
||||
'@jsr': 'https://npm.jsr.io/',
|
||||
}
|
||||
|
||||
describe('licences', () => {
|
||||
|
||||
@@ -155,6 +155,7 @@ export interface PackageResponse {
|
||||
// If latest does not equal the version of the
|
||||
// resolved package, it is out-of-date.
|
||||
latest?: string
|
||||
alias?: string
|
||||
} & (
|
||||
{
|
||||
isLocal: true
|
||||
|
||||
Reference in New Issue
Block a user