From 990cdcba47f76ce7ed42879216c2821655a451ca Mon Sep 17 00:00:00 2001 From: Basix Date: Fri, 27 Sep 2024 04:34:31 +0900 Subject: [PATCH 1/2] fix(plugin-npm): add resolver for converting locators with __archiveUrl --- .yarn/versions/34906c89.yml | 24 +++++++++ .../plugin-npm/sources/NpmTarballResolver.ts | 49 +++++++++++++++++++ packages/plugin-npm/sources/index.ts | 2 + 3 files changed, 75 insertions(+) create mode 100644 .yarn/versions/34906c89.yml create mode 100644 packages/plugin-npm/sources/NpmTarballResolver.ts diff --git a/.yarn/versions/34906c89.yml b/.yarn/versions/34906c89.yml new file mode 100644 index 000000000000..f198b962fb44 --- /dev/null +++ b/.yarn/versions/34906c89.yml @@ -0,0 +1,24 @@ +releases: + "@yarnpkg/cli": patch + "@yarnpkg/plugin-npm": patch + +declined: + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/builder" + - "@yarnpkg/core" + - "@yarnpkg/doctor" diff --git a/packages/plugin-npm/sources/NpmTarballResolver.ts b/packages/plugin-npm/sources/NpmTarballResolver.ts new file mode 100644 index 000000000000..14063a3b8654 --- /dev/null +++ b/packages/plugin-npm/sources/NpmTarballResolver.ts @@ -0,0 +1,49 @@ +import {Descriptor, Locator, MinimalResolveOptions, ResolveOptions, Resolver, Package} from '@yarnpkg/core'; +import {structUtils} from '@yarnpkg/core'; + +import {PROTOCOL} from './constants'; + +export class NpmTarballResolver implements Resolver { + supportsDescriptor(descriptor: Descriptor, opts: MinimalResolveOptions) { + if (!descriptor.range.startsWith(PROTOCOL)) + return false; + + const {params} = structUtils.parseRange(descriptor.range); + if (params === null || typeof params.__archiveUrl !== `string`) + return false; + + return true; + } + + supportsLocator(locator: Locator, opts: MinimalResolveOptions) { + // Once transformed into locators, the descriptors are resolved by the NpmSemverResolver + return false; + } + + shouldPersistResolution(locator: Locator, opts: MinimalResolveOptions): never { + // Once transformed into locators, the descriptors are resolved by the NpmSemverResolver + throw new Error(`Unreachable`); + } + + bindDescriptor(descriptor: Descriptor, fromLocator: Locator, opts: MinimalResolveOptions) { + return descriptor; + } + + getResolutionDependencies(descriptor: Descriptor, opts: MinimalResolveOptions) { + return {}; + } + + async getCandidates(descriptor: Descriptor, dependencies: Record, opts: ResolveOptions) { + return [structUtils.convertDescriptorToLocator(descriptor)]; + } + + async getSatisfying(descriptor: Descriptor, dependencies: Record, locators: Array, opts: ResolveOptions) { + const baseLocator = structUtils.convertDescriptorToLocator(descriptor); + return {locators: locators.filter(locator => structUtils.areLocatorsEqual(locator, baseLocator)), sorted: false}; + } + + resolve(locator: Locator, opts: ResolveOptions): never { + // Once transformed into locators, the descriptors are resolved by the NpmSemverResolver + throw new Error(`Unreachable`); + } +} diff --git a/packages/plugin-npm/sources/index.ts b/packages/plugin-npm/sources/index.ts index 3c1cf5b15827..ba24a48c8d71 100644 --- a/packages/plugin-npm/sources/index.ts +++ b/packages/plugin-npm/sources/index.ts @@ -5,6 +5,7 @@ import {NpmRemapResolver} from './NpmRemapR import {NpmSemverFetcher} from './NpmSemverFetcher'; import {NpmSemverResolver} from './NpmSemverResolver'; import {NpmTagResolver} from './NpmTagResolver'; +import {NpmTarballResolver} from './NpmTarballResolver'; import * as npmConfigUtils from './npmConfigUtils'; import * as npmHttpUtils from './npmHttpUtils'; import * as npmPublishUtils from './npmPublishUtils'; @@ -150,6 +151,7 @@ const plugin: Plugin = { NpmRemapResolver, NpmSemverResolver, NpmTagResolver, + NpmTarballResolver, ], }; From 72906d61ad294f4673deef862dcf651740f511d7 Mon Sep 17 00:00:00 2001 From: Shrey Somaiya Date: Sun, 28 Sep 2025 16:36:11 +1000 Subject: [PATCH 2/2] chore: add tests for npmtarballresolver --- .../tests/NpmTarballResolver.test.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 packages/plugin-npm/tests/NpmTarballResolver.test.ts diff --git a/packages/plugin-npm/tests/NpmTarballResolver.test.ts b/packages/plugin-npm/tests/NpmTarballResolver.test.ts new file mode 100644 index 000000000000..6615fc42419b --- /dev/null +++ b/packages/plugin-npm/tests/NpmTarballResolver.test.ts @@ -0,0 +1,77 @@ +import {structUtils, Configuration, Project, StreamReport} from '@yarnpkg/core'; + +import {NpmTarballResolver} from '../sources/NpmTarballResolver'; + +import {makeConfiguration} from './_makeConfiguration'; + +describe(`NpmTarballResolver`, () => { + let configuration: Configuration; + let project: Project; + let resolver: NpmTarballResolver; + + beforeEach(async () => { + configuration = await makeConfiguration(); + project = new Project(configuration.projectCwd!, {configuration}); + resolver = new NpmTarballResolver(); + }); + describe(`supportsDescriptor`, () => { + it(`should support npm descriptors with __archiveUrl parameters`, () => { + const ident = structUtils.makeIdent(null, `test-package`); + const descriptor = structUtils.makeDescriptor(ident, `npm:1.0.0::__archiveUrl=https://registry.example.org/test-package-1.0.0.tgz`); + + expect(resolver.supportsDescriptor(descriptor, {project, resolver})).toBe(true); + }); + + it(`should not support npm descriptors without __archiveUrl parameters`, () => { + const ident = structUtils.makeIdent(null, `test-package`); + const descriptor = structUtils.makeDescriptor(ident, `npm:1.0.0`); + + expect(resolver.supportsDescriptor(descriptor, {project, resolver})).toBe(false); + }); + + it(`should not support non-npm descriptors`, () => { + const ident = structUtils.makeIdent(null, `test-package`); + const descriptor = structUtils.makeDescriptor(ident, `file:./local-package.tgz`); + + expect(resolver.supportsDescriptor(descriptor, {project, resolver})).toBe(false); + }); + }); + + describe(`supportsLocator`, () => { + it(`should not support any locators (handled by NpmSemverResolver)`, () => { + const ident = structUtils.makeIdent(null, `test-package`); + const locator = structUtils.makeLocator(ident, `npm:1.0.0::__archiveUrl=https://registry.example.org/test-package-1.0.0.tgz`); + + expect(resolver.supportsLocator(locator, {project, resolver})).toBe(false); + }); + }); + + describe(`getCandidates`, () => { + it(`should convert descriptor to locator`, async () => { + const ident = structUtils.makeIdent(null, `test-package`); + const descriptor = structUtils.makeDescriptor(ident, `npm:1.0.0::__archiveUrl=https://registry.example.org/test-package-1.0.0.tgz`); + const report = new StreamReport({stdout: process.stdout, configuration}); + + const candidates = await resolver.getCandidates(descriptor, {}, {project, resolver, report}); + + expect(candidates.length).toBe(1); + expect(candidates[0].identHash).toBe(descriptor.identHash); + }); + }); + + describe(`getSatisfying`, () => { + it(`should filter locators that match the descriptor`, async () => { + const ident = structUtils.makeIdent(null, `test-package`); + const descriptor = structUtils.makeDescriptor(ident, `npm:1.0.0::__archiveUrl=https://registry.example.org/test-package-1.0.0.tgz`); + const locator1 = structUtils.makeLocator(ident, `npm:1.0.0::__archiveUrl=https://registry.example.org/test-package-1.0.0.tgz`); + const locator2 = structUtils.makeLocator(ident, `npm:2.0.0::__archiveUrl=https://registry.example.org/test-package-2.0.0.tgz`); + const report = new StreamReport({stdout: process.stdout, configuration}); + + const result = await resolver.getSatisfying(descriptor, {}, [locator1, locator2], {project, resolver, report}); + + expect(result.locators.length).toBe(1); + expect(result.locators[0].locatorHash).toBe(locator1.locatorHash); + expect(result.sorted).toBe(false); + }); + }); +});