{"record":{"id":"b7a29ffecceeaefe","repo":"angular/angular-cli","slug":"could-not-parse-package-name-from-specifier-spe","errorCode":null,"errorMessage":"Could not parse package name from specifier: ${specifier}","messagePattern":"Could not parse package name from specifier: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/angular/cli/src/package-managers/package-manager.ts","lineNumber":571,"sourceCode":"   * as those specified by file paths, directory paths, and remote tarballs.\n   * Caching is only supported for registry packages.\n   *\n   * @param specifier The package specifier to resolve the manifest for.\n   * @param options Options for the fetch.\n   * @returns A promise that resolves to the `PackageManifest` object, or `null` if the package is not found.\n   */\n  async getManifest(\n    specifier: string | npa.Result,\n    options: { timeout?: number; registry?: string; bypassCache?: boolean } = {},\n  ): Promise<PackageManifest | null> {\n    const { name, type, fetchSpec } = typeof specifier === 'string' ? npa(specifier) : specifier;\n\n    switch (type) {\n      case 'range':\n      case 'version':\n      case 'tag': {\n        if (!name) {\n          throw new Error(`Could not parse package name from specifier: ${specifier}`);\n        }\n\n        // `fetchSpec` is the version, range, or tag.\n        let versionSpec = fetchSpec ?? 'latest';\n        if (this.descriptor.requiresManifestVersionLookup) {\n          if (type === 'tag' || !fetchSpec) {\n            const metadata = await this.getRegistryMetadata(name, options);\n            if (!metadata) {\n              return null;\n            }\n            versionSpec = metadata['dist-tags'][versionSpec];\n          } else if (type === 'range') {\n            const metadata = await this.getRegistryMetadata(name, options);\n            if (!metadata) {\n              return null;\n            }\n            versionSpec = maxSatisfying(metadata.versions, fetchSpec) ?? '';\n          }","sourceCodeStart":553,"sourceCodeEnd":589,"githubUrl":"https://github.com/angular/angular-cli/blob/bb72145f9ab45aee29f523236b3a25cd0813a841/packages/angular/cli/src/package-managers/package-manager.ts#L553-L589","documentation":"In getManifest(), the specifier is parsed with npm-package-arg (npa). For 'range', 'version', and 'tag' specifier types the resolved `name` must be present to query the registry; if npa could not extract a package name from the given specifier, this Error is thrown. It indicates the specifier string is malformed in a way that yielded a registry-style type but no name.","triggerScenarios":"Calling manifest(specifier) with a string that npa resolves to type range/version/tag but with a falsy `name` — e.g. an empty or whitespace string coerced into a range type, a bare '@scope/' prefix with no package name, or passing a hand-built npa.Result object with name undefined.","commonSituations":"Typing a specifier like '@scope/' or '' into a tool that forwards it to the CLI; programmatically constructing package specifiers from config values that are missing the package name (e.g. `${scope}/${''}@${version}`); trimming bugs that leave '@' or version delimiters only.","solutions":["Inspect the specifier string and supply a complete one including the package name, e.g. '@angular/core@^17' instead of '@scope/' or ''.","Validate the specifier with npa before calling: check that the parsed result has a non-empty `name` for range/version/tag types.","If building specifiers from variables, add a check that the name segment is non-empty before interpolation.","If the target is a local path or tarball, use the correct specifier form ('file:./path', 'directory', URL) so it parses to the right type."],"exampleFix":"// before\nawait pm.getManifest('@myorg/');\n\n// after\nconst spec = '@myorg/my-package@^1.0.0';\nconst parsed = npa(spec);\nif (!parsed.name) {\n  throw new Error(`Specifier must include a package name: ${spec}`);\n}\nawait pm.getManifest(spec);","handlingStrategy":"validation","validationCode":"import npa from 'npm-package-arg';\nfunction assertNamedRegistrySpec(specifier: string): npa.Result {\n  const parsed = npa(specifier);\n  if ((parsed.type === 'range' || parsed.type === 'version' || parsed.type === 'tag') && !parsed.name) {\n    throw new Error(`Specifier must include a package name: \"${specifier}\"`);\n  }\n  return parsed;\n}\n// call before pm.getManifest(specifier): assertNamedRegistrySpec(specifier);","typeGuard":"function hasName(parsed: npa.Result): parsed is npa.Result & { name: string } {\n  return typeof parsed.name === 'string' && parsed.name.length > 0;\n}","tryCatchPattern":"try {\n  const manifest = await pm.getManifest(specifier);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Could not parse package name from specifier')) {\n    console.error(`Fix specifier, include a package name: ${e.message}`);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Always include the full package name in specifiers: '@scope/name@version', never '@scope/' or ''.","Validate config-sourced specifier strings with npa before passing them to getManifest().","Trim and sanitize user/tool input so empty names or stray '@' characters cannot reach the API.","Use the correct specifier scheme for non-registry targets ('file:', URLs, tarballs) so npa resolves the intended type.","Add unit tests covering all specifier forms your tooling generates."],"tags":["specifier","npm-package-arg","validation","angular-cli"],"backgroundTag":"malformed-package-specifier","analyzedSha":"bb72145f9ab45aee29f523236b3a25cd0813a841","analyzedAt":"2026-08-30T02:47:34.745Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}