{"record":{"id":"1c87b36ab23c0767","repo":"angular/angular-cli","slug":"could-not-parse-directory-path-from-specifier-s","errorCode":null,"errorMessage":"Could not parse directory path from specifier: ${specifier}","messagePattern":"Could not parse directory path from specifier: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/angular/cli/src/package-managers/package-manager.ts","lineNumber":599,"sourceCode":"            }\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          }\n          if (!versionSpec) {\n            return null;\n          }\n        }\n\n        return this.getRegistryManifest(name, versionSpec, options);\n      }\n      case 'directory': {\n        if (!fetchSpec) {\n          throw new Error(`Could not parse directory path from specifier: ${specifier}`);\n        }\n\n        const manifestPath = join(fetchSpec, 'package.json');\n        const manifest = await this.host.readFile(manifestPath);\n\n        return JSON.parse(manifest);\n      }\n      case 'file':\n      case 'remote':\n      case 'git': {\n        if (!fetchSpec) {\n          throw new Error(`Could not parse location from specifier: ${specifier}`);\n        }\n\n        // Caching is not supported for non-registry specifiers.\n        const { workingDirectory, cleanup } = await this.acquireTempPackage(fetchSpec, {\n          ...options,\n          ignoreScripts: true,","sourceCodeStart":581,"sourceCodeEnd":617,"githubUrl":"https://github.com/angular/angular-cli/blob/bb72145f9ab45aee29f523236b3a25cd0813a841/packages/angular/cli/src/package-managers/package-manager.ts#L581-L617","documentation":"In getManifest(), when npa resolves the specifier to type 'directory', the resolved `fetchSpec` is the directory path; the manifest is read from `<dir>/package.json`. If `fetchSpec` is falsy — meaning npa classified it as a directory but could not extract the actual path — this Error is thrown. It signals a malformed directory-style specifier.","triggerScenarios":"Calling manifest(specifier) with a directory specifier such as 'file:./', 'file:', or a npa.Result of type 'directory' whose fetchSpec resolved to an empty string, so no directory path is available to read package.json from.","commonSituations":"Passing 'file:' with an empty path from templated config; referencing a directory specifier whose path portion was stripped by over-trimming; programmatic specifier assembly where the path variable was empty or undefined; workspace globs resolved to empty strings.","solutions":["Pass a complete directory specifier with a non-empty path, e.g. 'file:./packages/my-lib' instead of 'file:' or 'file:./'.","Resolve the path to an absolute directory with path.resolve() before constructing the specifier.","Verify the referenced directory exists and contains a package.json before calling manifest().","If building specifiers from config variables, validate the path segment is non-empty and a real directory first."],"exampleFix":"// before\nawait pm.getManifest('file:');\n\n// after\nimport { resolve, existsSync } from 'node:fs';\nconst dir = resolve('packages/my-lib');\nif (!existsSync(resolve(dir, 'package.json'))) {\n  throw new Error(`No package.json in directory: ${dir}`);\n}\nawait pm.getManifest(`file:${dir}`);","handlingStrategy":"validation","validationCode":"import npa from 'npm-package-arg';\nimport { existsSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nfunction assertDirectorySpec(specifier: string): string {\n  const parsed = npa(specifier);\n  if (parsed.type === 'directory') {\n    if (!parsed.fetchSpec) {\n      throw new Error(`Directory specifier missing a path: \"${specifier}\"`);\n    }\n    const dir = resolve(parsed.fetchSpec);\n    if (!existsSync(dir) || !statSync(dir).isDirectory() || !existsSync(resolve(dir, 'package.json'))) {\n      throw new Error(`Directory has no package.json: ${dir}`);\n    }\n    return dir;\n  }\n  return specifier;\n}\n// call before pm.getManifest(specifier): assertDirectorySpec(specifier);","typeGuard":"function hasDirectoryPath(parsed: npa.Result): parsed is npa.Result & { fetchSpec: string } {\n  return parsed.type === 'directory' && typeof parsed.fetchSpec === 'string' && parsed.fetchSpec.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 directory path from specifier')) {\n    console.error(`Provide a non-empty directory path, e.g. file:./packages/lib: ${e.message}`);\n  } else {\n    throw e;\n  }\n}","preventionTips":["Always pass a non-empty path in directory specifiers: 'file:./path/to/pkg', never 'file:' alone.","Resolve relative paths to absolute with path.resolve() before building the specifier.","Check the target directory contains a package.json before calling getManifest().","Validate config/workspace variables that feed into directory specifiers are non-empty strings.","Prefer npa() pre-parsing to confirm type === 'directory' and fetchSpec is set."],"tags":["specifier","directory","npm-package-arg","angular-cli"],"backgroundTag":"malformed-package-specifier","analyzedSha":"bb72145f9ab45aee29f523236b3a25cd0813a841","analyzedAt":"2026-08-30T02:47:34.745Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}