···11import * as v from 'valibot';
2233+import type { Registry } from '../../lib/package-name';
44+35import { FetchError, InvalidSpecifierError, PackageNotFoundError } from './errors';
44-import { abbreviatedPackumentSchema, type AbbreviatedPackument, type Registry } from './types';
66+import { abbreviatedPackumentSchema, type AbbreviatedPackument } from './types';
5768const NPM_REGISTRY = 'https://registry.npmjs.org';
79const JSR_REGISTRY = 'https://npm.jsr.io';
+9-40
src/npm/lib/resolve.ts
···11import * as semver from 'semver';
2233+import { parsePackageSpecifier } from '../../lib/package-name';
44+import type { Registry } from '../../lib/package-name';
35import { progress } from '../events';
4657import { InvalidSpecifierError, NoMatchingVersionError } from './errors';
68import { fetchPackument, reverseJsrName } from './registry';
77-import type {
88- AbbreviatedManifest,
99- PackageSpecifier,
1010- Registry,
1111- ResolvedPackage,
1212- ResolutionResult,
1313-} from './types';
99+import type { AbbreviatedManifest, PackageSpecifier, ResolvedPackage, ResolutionResult } from './types';
14101511/**
1612 * parses a package specifier string into name, range, and registry.
···2622 * @returns parsed specifier with name, range, and registry
2723 */
2824export function parseSpecifier(spec: string): PackageSpecifier {
2929- let registry: Registry = 'npm';
3030- let rest = spec;
3131-3232- // check for registry prefixes
3333- if (spec.startsWith('jsr:')) {
3434- registry = 'jsr';
3535- rest = spec.slice(4); // remove "jsr:"
3636- } else if (spec.startsWith('npm:')) {
3737- rest = spec.slice(4); // remove "npm:", registry already 'npm'
2525+ const parsed = parsePackageSpecifier(spec);
2626+ if (!parsed) {
2727+ throw new InvalidSpecifierError(spec, `invalid package specifier`);
3828 }
3939-4040- // handle scoped packages: @scope/name or @scope/name@version
4141- if (rest.startsWith('@')) {
4242- const slashIdx = rest.indexOf('/');
4343- if (slashIdx === -1) {
4444- throw new InvalidSpecifierError(spec, 'scoped package missing slash');
4545- }
4646- const atIdx = rest.indexOf('@', slashIdx);
4747- if (atIdx === -1) {
4848- return { name: rest, range: 'latest', registry };
4949- }
5050- return { name: rest.slice(0, atIdx), range: rest.slice(atIdx + 1), registry };
2929+ if (parsed.registry === 'jsr' && !parsed.name.startsWith('@')) {
3030+ throw new InvalidSpecifierError(spec, `JSR packages must be scoped`);
5131 }
5252-5353- // JSR packages must be scoped
5454- if (registry === 'jsr') {
5555- throw new InvalidSpecifierError(spec, 'JSR packages must be scoped');
5656- }
5757-5858- // handle regular packages: name or name@version
5959- const atIdx = rest.indexOf('@');
6060- if (atIdx === -1) {
6161- return { name: rest, range: 'latest', registry };
6262- }
6363- return { name: rest.slice(0, atIdx), range: rest.slice(atIdx + 1), registry };
3232+ return parsed;
6433}
65346635/**