diff --git a/.VERSION b/.VERSION index 97d8b700b9e..93cc76c725e 100644 --- a/.VERSION +++ b/.VERSION @@ -1,5 +1,5 @@ -refs=$Format:%D$ -commit=$Format:%H$ -abbrev_commit=$Format:%h$ -committer_date=$Format:%cs$ -git_describe=$Format:%(describe)$ +refs=HEAD -> develop +commit=ea42c1d09d38444c6206a245d67ca3d99930c9b0 +abbrev_commit=ea42c1d09d3 +committer_date=2026-05-26 +git_describe=v33.0.0rc1-4-gea42c1d09d3 diff --git a/src/packagedcode/__init__.py b/src/packagedcode/__init__.py index fc1e490eeff..48977f5ef56 100644 --- a/src/packagedcode/__init__.py +++ b/src/packagedcode/__init__.py @@ -152,6 +152,7 @@ npm.PnpmShrinkwrapYamlHandler, npm.PnpmLockYamlHandler, npm.PnpmWorkspaceYamlHandler, + npm.NpmrcHandler, nuget.NugetNupkgHandler, nuget.NugetNuspecHandler, diff --git a/src/packagedcode/npm.py b/src/packagedcode/npm.py index 7618e830c19..c8fd06989a7 100644 --- a/src/packagedcode/npm.py +++ b/src/packagedcode/npm.py @@ -2023,3 +2023,38 @@ def keywords_mapper(keywords, package): package.keywords = keywords return package + + +class NpmrcHandler(models.NonAssemblableDatafileHandler): + datasource_id = 'npmrc' + path_patterns = ( + '*/.npmrc', + ) + default_package_type = 'npm' + default_primary_language = 'JavaScript' + description = 'npmrc configuration file' + documentation_url = 'https://docs.npmjs.com/cli/v9/configuring-npm/npmrc' + + @classmethod + def parse(cls, location, package_only=False): + """ + Parses an .npmrc file and yields package data. + """ + extra_data = {} + with io.open(location, encoding='utf-8') as f: + for line in f: + stripped = line.strip() + if not stripped or stripped.startswith('#') or stripped.startswith(';'): + continue + if '=' in stripped: + key, _, value = stripped.partition('=') + extra_data[key.strip()] = value.strip() + + package_data = dict( + datasource_id=cls.datasource_id, + type=cls.default_package_type, + primary_language=cls.default_primary_language, + extra_data=extra_data, + ) + yield models.PackageData.from_data(package_data, package_only) + diff --git a/tests/packagedcode/data/npm/npmrc/.npmrc b/tests/packagedcode/data/npm/npmrc/.npmrc new file mode 100644 index 00000000000..12be2b325e8 --- /dev/null +++ b/tests/packagedcode/data/npm/npmrc/.npmrc @@ -0,0 +1,6 @@ +# this is a comment +; another comment + +registry=https://registry.npmjs.org/ +@my-scope:registry=https://npm.pkg.github.com +always-auth=true diff --git a/tests/packagedcode/data/npm/npmrc/.npmrc-expected.json b/tests/packagedcode/data/npm/npmrc/.npmrc-expected.json new file mode 100644 index 00000000000..4b0a3988185 --- /dev/null +++ b/tests/packagedcode/data/npm/npmrc/.npmrc-expected.json @@ -0,0 +1,50 @@ +[ + { + "type": "npm", + "namespace": null, + "name": null, + "version": null, + "qualifiers": {}, + "subpath": null, + "primary_language": "JavaScript", + "description": null, + "release_date": null, + "parties": [], + "keywords": [], + "homepage_url": null, + "download_url": null, + "size": null, + "sha1": null, + "md5": null, + "sha256": null, + "sha512": null, + "bug_tracking_url": null, + "code_view_url": null, + "vcs_url": null, + "copyright": null, + "holder": null, + "declared_license_expression": null, + "declared_license_expression_spdx": null, + "license_detections": [], + "other_license_expression": null, + "other_license_expression_spdx": null, + "other_license_detections": [], + "extracted_license_statement": null, + "notice_text": null, + "source_packages": [], + "file_references": [], + "is_private": false, + "is_virtual": false, + "extra_data": { + "registry": "https://registry.npmjs.org/", + "@my-scope:registry": "https://npm.pkg.github.com", + "always-auth": "true" + }, + "dependencies": [], + "repository_homepage_url": null, + "repository_download_url": null, + "api_data_url": null, + "datasource_id": "npmrc", + "purl": null + } +] diff --git a/tests/packagedcode/data/pypi/unpacked_sdist/metadata-2.1/commoncode-21.5.12/pyproject.toml b/tests/packagedcode/data/pypi/unpacked_sdist/metadata-2.1/commoncode-21.5.12/pyproject.toml index 8eebe91a559..0e7211f7b91 100644 --- a/tests/packagedcode/data/pypi/unpacked_sdist/metadata-2.1/commoncode-21.5.12/pyproject.toml +++ b/tests/packagedcode/data/pypi/unpacked_sdist/metadata-2.1/commoncode-21.5.12/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [tool.setuptools_scm] # this is used populated when creating a git archive # and when there is .git dir and/or there is no git installed -fallback_version = "v9999.$Format:%h-%cs$" +fallback_version = "v9999.ea42c1d09d3-2026-05-26" [tool.pytest.ini_options] norecursedirs = [ diff --git a/tests/packagedcode/test_npm.py b/tests/packagedcode/test_npm.py index 0be0bfe0ca1..29d6a36f658 100644 --- a/tests/packagedcode/test_npm.py +++ b/tests/packagedcode/test_npm.py @@ -550,6 +550,17 @@ def test_npm_scan_with_private_package_json_and_yarn_lock(self): expected_file, result_file, remove_uuid=True, regen=REGEN_TEST_FIXTURES ) + def test_is_datafile_npmrc(self): + test_file = self.get_test_loc('npm/npmrc/.npmrc') + assert npm.NpmrcHandler.is_datafile(test_file) + + def test_parse_npmrc(self): + test_file = self.get_test_loc('npm/npmrc/.npmrc') + expected_loc = self.get_test_loc('npm/npmrc/.npmrc-expected.json') + packages = npm.NpmrcHandler.parse(test_file) + self.check_packages_data(packages, expected_loc, regen=REGEN_TEST_FIXTURES) + + test_data = [ (['MIT'], 'mit'),