@vltpkg/satisfies
Advanced tools
+26
-22
| { | ||
| "name": "@vltpkg/satisfies", | ||
| "description": "method for determining if a DepID satisfies a Spec", | ||
| "version": "1.0.0-rc.18", | ||
| "version": "1.0.0-rc.22", | ||
| "repository": { | ||
@@ -10,9 +10,12 @@ "type": "git", | ||
| }, | ||
| "author": "vlt technology inc. <support@vlt.sh> (http://vlt.sh)", | ||
| "author": { | ||
| "name": "vlt technology inc.", | ||
| "email": "support@vlt.sh" | ||
| }, | ||
| "dependencies": { | ||
| "@vltpkg/dep-id": "1.0.0-rc.18", | ||
| "@vltpkg/error-cause": "1.0.0-rc.18", | ||
| "@vltpkg/spec": "1.0.0-rc.18", | ||
| "@vltpkg/workspaces": "1.0.0-rc.18", | ||
| "@vltpkg/semver": "1.0.0-rc.18" | ||
| "@vltpkg/dep-id": "1.0.0-rc.22", | ||
| "@vltpkg/error-cause": "1.0.0-rc.22", | ||
| "@vltpkg/semver": "1.0.0-rc.22", | ||
| "@vltpkg/spec": "1.0.0-rc.22", | ||
| "@vltpkg/workspaces": "1.0.0-rc.22" | ||
| }, | ||
@@ -31,4 +34,15 @@ "devDependencies": { | ||
| "engines": { | ||
| "node": ">=22.9.0" | ||
| "node": ">=22.22.0" | ||
| }, | ||
| "scripts": { | ||
| "format": "prettier --write . --log-level warn --ignore-path ../../.prettierignore --cache", | ||
| "format:check": "prettier --check . --ignore-path ../../.prettierignore --cache", | ||
| "lint": "eslint . --fix", | ||
| "lint:check": "eslint .", | ||
| "prepack": "tsc -p tsconfig.publish.json && ../../scripts/update-dist-exports.ts", | ||
| "snap": "tap", | ||
| "test": "tap", | ||
| "posttest": "tsc --noEmit", | ||
| "typecheck": "tsc --noEmit" | ||
| }, | ||
| "tap": { | ||
@@ -38,3 +52,3 @@ "extends": "../../tap-config.yaml" | ||
| "prettier": "../../.prettierrc.js", | ||
| "module": "./dist/index.js", | ||
| "module": "./src/index.ts", | ||
| "type": "module", | ||
@@ -45,3 +59,3 @@ "exports": { | ||
| "import": { | ||
| "default": "./dist/index.js" | ||
| "default": "./src/index.ts" | ||
| } | ||
@@ -52,13 +66,3 @@ } | ||
| "dist" | ||
| ], | ||
| "scripts": { | ||
| "format": "prettier --write . --log-level warn --ignore-path ../../.prettierignore --cache", | ||
| "format:check": "prettier --check . --ignore-path ../../.prettierignore --cache", | ||
| "lint": "eslint . --fix", | ||
| "lint:check": "eslint .", | ||
| "snap": "tap", | ||
| "test": "tap", | ||
| "posttest": "tsc --noEmit", | ||
| "typecheck": "tsc --noEmit" | ||
| } | ||
| } | ||
| ] | ||
| } |
| import type { DepID, DepIDTuple } from '@vltpkg/dep-id'; | ||
| import { Spec } from '@vltpkg/spec'; | ||
| import { Monorepo } from '@vltpkg/workspaces'; | ||
| /** | ||
| * Return true if the node referenced by this DepID would satisfy the | ||
| * supplied Spec object. | ||
| */ | ||
| export declare const satisfies: (id: DepID | undefined, spec: Spec, fromLocation?: string, projectRoot?: string, monorepo?: Monorepo) => boolean; | ||
| export declare const satisfiesTuple: (tuple: DepIDTuple, spec: Spec, fromLocation?: string, projectRoot?: string, monorepo?: Monorepo) => boolean; | ||
| //# sourceMappingURL=index.d.ts.map |
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAGvD,OAAO,EAAuB,IAAI,EAAE,MAAM,cAAc,CAAA;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAG7C;;;GAGG;AACH,eAAO,MAAM,SAAS,OAChB,KAAK,GAAG,SAAS,QACf,IAAI,0DAGC,QAAQ,KAClB,OAQA,CAAA;AAEH,eAAO,MAAM,cAAc,UAClB,UAAU,QACX,IAAI,0DAGC,QAAQ,KAClB,OAwGF,CAAA"} |
-122
| import { splitDepID } from '@vltpkg/dep-id'; | ||
| import { error } from '@vltpkg/error-cause'; | ||
| import { parse, Version } from '@vltpkg/semver'; | ||
| import { defaultRegistryName, Spec } from '@vltpkg/spec'; | ||
| import { Monorepo } from '@vltpkg/workspaces'; | ||
| import { relative, resolve } from 'node:path'; | ||
| /** | ||
| * Return true if the node referenced by this DepID would satisfy the | ||
| * supplied Spec object. | ||
| */ | ||
| export const satisfies = (id, spec, fromLocation = process.cwd(), projectRoot = process.cwd(), monorepo) => !!id && | ||
| satisfiesTuple(splitDepID(id), spec, fromLocation, projectRoot, monorepo); | ||
| export const satisfiesTuple = (tuple, spec, fromLocation = process.cwd(), projectRoot = process.cwd(), monorepo) => { | ||
| const { options } = spec; | ||
| spec = spec.final; | ||
| const [type, first, second] = tuple; | ||
| if (spec.type !== type) | ||
| return false; | ||
| switch (spec.type) { | ||
| case 'registry': { | ||
| /* c8 ignore start - should be impossible */ | ||
| if (!first) { | ||
| // must be from the default registry | ||
| if (spec.registry !== options.registry) { | ||
| return false; | ||
| } | ||
| /* c8 ignore stop */ | ||
| } | ||
| else { | ||
| let namedRegistry = options.registries[first]; | ||
| /* c8 ignore next 3 */ | ||
| if (!namedRegistry && first === defaultRegistryName) { | ||
| namedRegistry = options.registry; | ||
| } | ||
| if (namedRegistry && namedRegistry !== spec.registry) { | ||
| // we know the name, and it's not the registry being used | ||
| return false; | ||
| } | ||
| else if (!namedRegistry && first !== spec.registry) { | ||
| // an explicit registry URL, but does not match | ||
| return false; | ||
| } | ||
| } | ||
| /* c8 ignore next */ | ||
| if (!second) | ||
| throw error('Invalid DepID', { found: tuple }); | ||
| const [name, version] = parseNameVer(second); | ||
| return ( | ||
| // mismatched name always invalid | ||
| name !== spec.name || !version ? false | ||
| // if just a dist-tag, assume valid | ||
| : !spec.range ? true | ||
| : spec.range.test(Version.parse(version))); | ||
| } | ||
| case 'file': { | ||
| /* c8 ignore next - should be impossible */ | ||
| if (spec.file === undefined) | ||
| return false; | ||
| const resolvedSpec = resolve(projectRoot, fromLocation, spec.file); | ||
| const resolvedId = resolve(projectRoot, first); | ||
| // valid if the relative path is '', refers to the same path | ||
| return !relative(resolvedSpec, resolvedId); | ||
| } | ||
| case 'workspace': { | ||
| monorepo ??= Monorepo.load(projectRoot); | ||
| /* c8 ignore next */ | ||
| if (!spec.workspace) | ||
| return false; | ||
| const fromID = monorepo.get(first); | ||
| const fromSpec = monorepo.get(spec.workspace); | ||
| if (fromID !== fromSpec || !fromSpec || !fromID) | ||
| return false; | ||
| if (!spec.range) | ||
| return true; | ||
| const v = parse(fromID.manifest.version ?? ''); | ||
| return !!v && spec.range.test(v); | ||
| } | ||
| case 'remote': { | ||
| return spec.remoteURL === first; | ||
| } | ||
| case 'git': { | ||
| const { gitRemote, gitSelectorParsed = {}, gitSelector, gitCommittish, namedGitHost, namedGitHostPath, } = spec; | ||
| if (gitRemote !== first) { | ||
| if (namedGitHost && namedGitHostPath) { | ||
| const ngh = `${namedGitHost}:`; | ||
| if (first.startsWith(ngh)) { | ||
| if (first !== ngh + namedGitHostPath) | ||
| return false; | ||
| } | ||
| else | ||
| return false; | ||
| } | ||
| else | ||
| return false; | ||
| } | ||
| if (gitSelector && !second) | ||
| return false; | ||
| /* c8 ignore next - always set to something, even if empty */ | ||
| const [parsed, committish] = Spec.parseGitSelector(second ?? ''); | ||
| if (gitCommittish && committish) | ||
| return committish.startsWith(gitCommittish); | ||
| for (const [k, v] of Object.entries(gitSelectorParsed)) { | ||
| if (parsed[k] !== v) | ||
| return false; | ||
| } | ||
| return true; | ||
| } | ||
| /* c8 ignore start */ | ||
| default: { | ||
| throw error('Invalid spec type', { spec }); | ||
| } | ||
| } | ||
| /* c8 ignore stop */ | ||
| }; | ||
| const parseNameVer = (nv) => { | ||
| const at = nv.lastIndexOf('@'); | ||
| // if it's 0, means @scoped without a version | ||
| return at <= 0 ? | ||
| [nv, ''] | ||
| : [nv.substring(0, at), nv.substring(at + 1)]; | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { splitDepID } from '@vltpkg/dep-id'\nimport type { DepID, DepIDTuple } from '@vltpkg/dep-id'\nimport { error } from '@vltpkg/error-cause'\nimport { parse, Version } from '@vltpkg/semver'\nimport { defaultRegistryName, Spec } from '@vltpkg/spec'\nimport type { GitSelectorParsed } from '@vltpkg/spec'\nimport { Monorepo } from '@vltpkg/workspaces'\nimport { relative, resolve } from 'node:path'\n\n/**\n * Return true if the node referenced by this DepID would satisfy the\n * supplied Spec object.\n */\nexport const satisfies = (\n id: DepID | undefined,\n spec: Spec,\n fromLocation = process.cwd(),\n projectRoot = process.cwd(),\n monorepo?: Monorepo,\n): boolean =>\n !!id &&\n satisfiesTuple(\n splitDepID(id),\n spec,\n fromLocation,\n projectRoot,\n monorepo,\n )\n\nexport const satisfiesTuple = (\n tuple: DepIDTuple,\n spec: Spec,\n fromLocation = process.cwd(),\n projectRoot = process.cwd(),\n monorepo?: Monorepo,\n): boolean => {\n const { options } = spec\n spec = spec.final\n const [type, first, second] = tuple\n if (spec.type !== type) return false\n\n switch (spec.type) {\n case 'registry': {\n /* c8 ignore start - should be impossible */\n if (!first) {\n // must be from the default registry\n if (spec.registry !== options.registry) {\n return false\n }\n /* c8 ignore stop */\n } else {\n let namedRegistry = options.registries[first]\n /* c8 ignore next 3 */\n if (!namedRegistry && first === defaultRegistryName) {\n namedRegistry = options.registry\n }\n if (namedRegistry && namedRegistry !== spec.registry) {\n // we know the name, and it's not the registry being used\n return false\n } else if (!namedRegistry && first !== spec.registry) {\n // an explicit registry URL, but does not match\n return false\n }\n }\n /* c8 ignore next */\n if (!second) throw error('Invalid DepID', { found: tuple })\n const [name, version] = parseNameVer(second)\n return (\n // mismatched name always invalid\n name !== spec.name || !version ? false\n // if just a dist-tag, assume valid\n : !spec.range ? true\n : spec.range.test(Version.parse(version))\n )\n }\n\n case 'file': {\n /* c8 ignore next - should be impossible */\n if (spec.file === undefined) return false\n const resolvedSpec = resolve(\n projectRoot,\n fromLocation,\n spec.file,\n )\n const resolvedId = resolve(projectRoot, first)\n // valid if the relative path is '', refers to the same path\n return !relative(resolvedSpec, resolvedId)\n }\n\n case 'workspace': {\n monorepo ??= Monorepo.load(projectRoot)\n /* c8 ignore next */\n if (!spec.workspace) return false\n const fromID = monorepo.get(first)\n const fromSpec = monorepo.get(spec.workspace)\n if (fromID !== fromSpec || !fromSpec || !fromID) return false\n if (!spec.range) return true\n const v = parse(fromID.manifest.version ?? '')\n return !!v && spec.range.test(v)\n }\n\n case 'remote': {\n return spec.remoteURL === first\n }\n\n case 'git': {\n const {\n gitRemote,\n gitSelectorParsed = {},\n gitSelector,\n gitCommittish,\n namedGitHost,\n namedGitHostPath,\n } = spec\n if (gitRemote !== first) {\n if (namedGitHost && namedGitHostPath) {\n const ngh = `${namedGitHost}:`\n if (first.startsWith(ngh)) {\n if (first !== ngh + namedGitHostPath) return false\n } else return false\n } else return false\n }\n if (gitSelector && !second) return false\n /* c8 ignore next - always set to something, even if empty */\n const [parsed, committish] = Spec.parseGitSelector(second ?? '')\n if (gitCommittish && committish)\n return committish.startsWith(gitCommittish)\n for (const [k, v] of Object.entries(gitSelectorParsed)) {\n if (parsed[k as keyof GitSelectorParsed] !== v) return false\n }\n return true\n }\n\n /* c8 ignore start */\n default: {\n throw error('Invalid spec type', { spec })\n }\n }\n /* c8 ignore stop */\n}\n\nconst parseNameVer = (nv: string): [string, string] => {\n const at = nv.lastIndexOf('@')\n // if it's 0, means @scoped without a version\n return at <= 0 ?\n [nv, '']\n : [nv.substring(0, at), nv.substring(at + 1)]\n}\n"]} |
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 5 instances in 1 package
4635
-76.05%3
-57.14%0
-100%6
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated
Updated