fad-checker 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +65 -0
- package/README.md +147 -25
- package/completions/fad-checker.bash +1 -1
- package/completions/fad-checker.zsh +14 -1
- package/data/eol-mapping.json +5 -5
- package/data/license-policy.json +97 -0
- package/fad-checker-report/cve-report.doc +630 -0
- package/fad-checker-report/cve-report.html +748 -0
- package/fad-checker.js +556 -245
- package/lib/cache-archive.js +3 -0
- package/lib/codecs/codec.interface.js +3 -0
- package/lib/codecs/composer/parse.js +3 -0
- package/lib/codecs/composer/registry.js +13 -5
- package/lib/codecs/composer.codec.js +3 -0
- package/lib/codecs/go/parse.js +65 -0
- package/lib/codecs/go/registry.js +74 -0
- package/lib/codecs/go.codec.js +76 -0
- package/lib/codecs/index.js +7 -2
- package/lib/codecs/maven/jar-scan.js +199 -0
- package/lib/codecs/maven.codec.js +17 -1
- package/lib/codecs/npm/collect.js +37 -17
- package/lib/codecs/npm/parse.js +117 -10
- package/lib/codecs/npm/registry.js +14 -5
- package/lib/codecs/npm.codec.js +4 -1
- package/lib/codecs/nuget/parse.js +3 -0
- package/lib/codecs/nuget/registry.js +11 -4
- package/lib/codecs/nuget.codec.js +7 -2
- package/lib/codecs/pypi/parse.js +111 -14
- package/lib/codecs/pypi/registry.js +24 -5
- package/lib/codecs/pypi.codec.js +30 -6
- package/lib/codecs/recipes.js +28 -1
- package/lib/codecs/ruby/parse.js +42 -0
- package/lib/codecs/ruby/registry.js +76 -0
- package/lib/codecs/ruby.codec.js +66 -0
- package/lib/codecs/select.js +3 -0
- package/lib/codecs/yarn.codec.js +3 -0
- package/lib/config.js +3 -0
- package/lib/core.js +38 -1
- package/lib/cpe.js +30 -5
- package/lib/csaf-export.js +159 -0
- package/lib/cve-download.js +3 -0
- package/lib/cve-match.js +27 -6
- package/lib/cve-report.js +157 -28
- package/lib/dep-record.js +15 -2
- package/lib/deps-descriptor.js +113 -0
- package/lib/epss.js +115 -0
- package/lib/gate.js +45 -0
- package/lib/json-export.js +110 -0
- package/lib/kev.js +88 -0
- package/lib/license-policy.js +110 -0
- package/lib/maven-license.js +52 -0
- package/lib/maven-repo.js +3 -0
- package/lib/maven-version.js +8 -3
- package/lib/nvd.js +17 -6
- package/lib/osv.js +75 -23
- package/lib/outdated.js +10 -2
- package/lib/priority.js +90 -0
- package/lib/purl.js +77 -0
- package/lib/retire.js +80 -13
- package/lib/sarif-export.js +134 -0
- package/lib/sbom-export.js +153 -0
- package/lib/scan-completeness.js +3 -0
- package/lib/snyk.js +3 -0
- package/lib/suppress.js +113 -0
- package/lib/transitive.js +6 -3
- package/lib/ui.js +90 -0
- package/package.json +49 -2
- package/CLAUDE.md +0 -147
- package/docs/ARCHITECTURE.md +0 -182
- package/docs/USAGE.md +0 -187
- package/docs/superpowers/plans/2026-05-29-codec-composer.md +0 -556
- package/docs/superpowers/plans/2026-05-29-codec-foundation.md +0 -851
- package/docs/superpowers/plans/2026-05-29-codec-nuget.md +0 -432
- package/docs/superpowers/plans/2026-05-29-codec-pypi.md +0 -450
- package/docs/superpowers/specs/2026-05-29-codecs-multi-ecosystem-design.md +0 -251
- package/test/cli-ecosystem.test.js +0 -30
- package/test/codec-edge-cases.test.js +0 -131
- package/test/codec-integration.test.js +0 -119
- package/test/codecs.test.js +0 -92
- package/test/composer.test.js +0 -71
- package/test/core.test.js +0 -153
- package/test/cpe.test.js +0 -166
- package/test/cve-download.test.js +0 -39
- package/test/cve-match.test.js +0 -217
- package/test/cve-report.test.js +0 -180
- package/test/dep-record.test.js +0 -31
- package/test/fixtures/complex-enterprise/api/pom.xml +0 -32
- package/test/fixtures/complex-enterprise/build/pom.xml +0 -46
- package/test/fixtures/complex-enterprise/dao/pom.xml +0 -37
- package/test/fixtures/complex-enterprise/pom.xml +0 -66
- package/test/fixtures/complex-enterprise/web/pom.xml +0 -77
- package/test/fixtures/csharp-config/packages.config +0 -4
- package/test/fixtures/csharp-csproj/Directory.Packages.props +0 -5
- package/test/fixtures/csharp-csproj/app.csproj +0 -7
- package/test/fixtures/csharp-lock/packages.lock.json +0 -6
- package/test/fixtures/cve-samples/cve-non-java.json +0 -19
- package/test/fixtures/cve-samples/cve-product-only.json +0 -31
- package/test/fixtures/cve-samples/cve-with-packagename.json +0 -37
- package/test/fixtures/cve-samples/nvd-log4shell.json +0 -40
- package/test/fixtures/cve-samples/nvd-npm-lodash.json +0 -22
- package/test/fixtures/monorepo-mixed/libs/common-bom/pom.xml +0 -26
- package/test/fixtures/monorepo-mixed/packages/cli/package.json +0 -14
- package/test/fixtures/monorepo-mixed/packages/cli/yarn.lock +0 -41
- package/test/fixtures/monorepo-mixed/packages/no-lock/package.json +0 -10
- package/test/fixtures/monorepo-mixed/packages/web-app/package-lock.json +0 -71
- package/test/fixtures/monorepo-mixed/packages/web-app/package.json +0 -17
- package/test/fixtures/monorepo-mixed/pom.xml +0 -29
- package/test/fixtures/monorepo-mixed/services/api/pom.xml +0 -27
- package/test/fixtures/monorepo-mixed/services/worker/pom.xml +0 -28
- package/test/fixtures/php-app/composer.json +0 -5
- package/test/fixtures/php-app/composer.lock +0 -10
- package/test/fixtures/polyglot/cs/packages.lock.json +0 -1
- package/test/fixtures/polyglot/js/package-lock.json +0 -1
- package/test/fixtures/polyglot/js/package.json +0 -1
- package/test/fixtures/polyglot/mvn/pom.xml +0 -7
- package/test/fixtures/polyglot/php/composer.lock +0 -1
- package/test/fixtures/polyglot/py/poetry.lock +0 -3
- package/test/fixtures/private-lib-detection/core/pom.xml +0 -26
- package/test/fixtures/private-lib-detection/plugin/pom.xml +0 -23
- package/test/fixtures/private-lib-detection/pom.xml +0 -35
- package/test/fixtures/python-pipenv/Pipfile.lock +0 -1
- package/test/fixtures/python-poetry/poetry.lock +0 -7
- package/test/fixtures/python-reqs/requirements.txt +0 -5
- package/test/fixtures/python-uv/uv.lock +0 -3
- package/test/fixtures/simple/app/pom.xml +0 -28
- package/test/fixtures/simple/lib/pom.xml +0 -18
- package/test/fixtures/simple/pom.xml +0 -24
- package/test/maven-repo.test.js +0 -111
- package/test/maven-version.test.js +0 -57
- package/test/monorepo.test.js +0 -132
- package/test/npm-registry.test.js +0 -64
- package/test/npm.test.js +0 -150
- package/test/nuget.test.js +0 -66
- package/test/osv.test.js +0 -62
- package/test/outdated.test.js +0 -101
- package/test/pypi.test.js +0 -72
- package/test/snyk.test.js +0 -64
- package/test/transitive.test.js +0 -305
- package/test/webjar.test.js +0 -33
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
# Design — Codecs & support multi-écosystème (C#/.NET, PHP, Python)
|
|
2
|
-
|
|
3
|
-
**Date** : 2026-05-29
|
|
4
|
-
**Statut** : approuvé (brainstorming) — prêt pour le plan d'implémentation
|
|
5
|
-
|
|
6
|
-
## Objectif
|
|
7
|
-
|
|
8
|
-
1. Réorganiser tout le code spécifique à un écosystème (aujourd'hui Maven + npm/yarn,
|
|
9
|
-
éparpillé dans `lib/core.js`, `lib/npm/*`, `lib/outdated.js`, `lib/osv.js`,
|
|
10
|
-
`lib/cve-report.js`, `fad-checker.js`) derrière une **interface codec** uniforme.
|
|
11
|
-
2. Ajouter trois écosystèmes en **parité complète** (vuln, EOL, obsolete, outdated,
|
|
12
|
-
recette de fix dans le report) : **C#/.NET (nuget)**, **PHP (composer)**, **Python (pypi)**.
|
|
13
|
-
|
|
14
|
-
## Décisions cadrées (brainstorming)
|
|
15
|
-
|
|
16
|
-
- **Parité complète d'emblée** pour les 3 nouveaux écosystèmes.
|
|
17
|
-
- **Big-bang** : extraction complète maven/npm vers des codecs (pas de cohabitation
|
|
18
|
-
« ancien monde / nouveau monde »). Le big-bang **réorganise, il ne réécrit pas** la
|
|
19
|
-
logique métier éprouvée (résolution POM/BOM, walker Maven Central, parsers lockfiles).
|
|
20
|
-
- **depRecord généralisé** : schéma neutre `{ ecosystem, namespace, name, version,
|
|
21
|
-
versions[], coordKey, manifestPaths[], … }`.
|
|
22
|
-
- **Règle de résolution de manifeste uniforme** (changement de philosophie) :
|
|
23
|
-
lockfile présent → on le prend ; sinon **fallback** sur le manifeste lâche
|
|
24
|
-
(`composer.json` / `requirements.txt` / `*.csproj` / `package.json`) **avec warning**.
|
|
25
|
-
Versions épinglées scannées, ranges/floating skippées individuellement + warning.
|
|
26
|
-
- **TOML** : dépendance légère **`smol-toml`** (maintenue, zéro dép transitive, TOML 1.0).
|
|
27
|
-
|
|
28
|
-
### ⚠️ Changement de contrat assumé (npm)
|
|
29
|
-
|
|
30
|
-
Comportement actuel : un `package.json` sans `package-lock.json`/`yarn.lock` est
|
|
31
|
-
**entièrement skippé** (warning chapitre 0). Nouveau comportement : il est **parsé en
|
|
32
|
-
best-effort** (versions épinglées) avec warning `no-lockfile — résultats partiels`.
|
|
33
|
-
Le test existant `no-lockfile` est adapté en conséquence (vérifie désormais le
|
|
34
|
-
fallback+warning, plus le skip total).
|
|
35
|
-
|
|
36
|
-
## Architecture
|
|
37
|
-
|
|
38
|
-
### L'interface codec
|
|
39
|
-
|
|
40
|
-
`lib/codecs/codec.interface.js` documente le contrat (objet, pas de classe imposée) :
|
|
41
|
-
|
|
42
|
-
```js
|
|
43
|
-
{
|
|
44
|
-
id, // "maven" | "npm" | "yarn" | "nuget" | "composer" | "pypi"
|
|
45
|
-
label, // "Maven", "npm", "NuGet", "Composer", "PyPI"
|
|
46
|
-
osvEcosystem, // "Maven" | "npm" | "NuGet" | "Packagist" | "PyPI" | null
|
|
47
|
-
|
|
48
|
-
manifestNames, // fichiers revendiqués par ce codec
|
|
49
|
-
detect(dir) -> bool, // ce repo contient-il mes manifestes ?
|
|
50
|
-
|
|
51
|
-
collect(dir, opts) -> { deps: Map<coordKey, depRecord>, warnings: [] },
|
|
52
|
-
|
|
53
|
-
coordKey(dep), // clé Map normalisée
|
|
54
|
-
formatCoord(dep), // affichage report
|
|
55
|
-
osvPackageName(dep), // nom pour OSV
|
|
56
|
-
|
|
57
|
-
checkRegistry(deps, opts) -> { outdated:[], deprecated:[] },
|
|
58
|
-
resolveEolProduct(dep) -> { product, label } | null,
|
|
59
|
-
|
|
60
|
-
recipe, // { label, pinSection, pinIntro, snippet(deps), directSection }
|
|
61
|
-
nativeScanners, // scanners au-delà du tronc commun OSV/NVD
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Le depRecord généralisé
|
|
66
|
-
|
|
67
|
-
```js
|
|
68
|
-
{
|
|
69
|
-
ecosystem, // "maven"|"npm"|"yarn"|"nuget"|"composer"|"pypi"
|
|
70
|
-
namespace, // groupId (maven) / scope @org (npm) / vendor (composer) / "" sinon
|
|
71
|
-
name, // artifactId / package name
|
|
72
|
-
version, // version représentative (la plus haute) pour affichage/EOL/outdated
|
|
73
|
-
versions[], // toutes les versions concrètes distinctes (CVE/OSV itèrent dessus)
|
|
74
|
-
coordKey, // clé de la Map, ex "maven:org.apache:log4j", "pypi:requests"
|
|
75
|
-
scope, isDev,
|
|
76
|
-
manifestPaths[], // remplace pomPaths/manifestPaths
|
|
77
|
-
// transitif : via, viaPaths, depth
|
|
78
|
-
// lock-specific : lockType, resolved, integrity
|
|
79
|
-
}
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
`coordKey` ne collisionne jamais entre écosystèmes (préfixe `ecosystem:`).
|
|
83
|
-
|
|
84
|
-
### Registre des codecs
|
|
85
|
-
|
|
86
|
-
`lib/codecs/index.js` :
|
|
87
|
-
- `getCodec(id)` → codec
|
|
88
|
-
- `allCodecs()` → tous les codecs enregistrés
|
|
89
|
-
- `detectCodecs(srcDir)` → codecs dont `detect()` est vrai
|
|
90
|
-
|
|
91
|
-
### Services partagés (agnostiques — plus aucun `if ecosystem === …`)
|
|
92
|
-
|
|
93
|
-
- `lib/osv.js` — querybatch groupé par `codec.osvEcosystem`, nom via `codec.osvPackageName`.
|
|
94
|
-
- `lib/nvd.js` — enrichment par CVE-id, totalement agnostique. **Activé pour tous les
|
|
95
|
-
écosystèmes** : dès qu'OSV remonte un `CVE-xxxx`, NVD/CPE s'appliquent.
|
|
96
|
-
- `lib/cpe.js` — refinement / filtrage faux positifs sur les matches, agnostique.
|
|
97
|
-
- Fetch `endoflife.date` — partagé ; le codec ne fournit que `{ product }`.
|
|
98
|
-
- Cache (`~/.fad-checker/`) — partagé.
|
|
99
|
-
|
|
100
|
-
### Scanners rattachés à un codec (`nativeScanners`)
|
|
101
|
-
|
|
102
|
-
- `maven` → CVE-index local cvelistV5 (`lib/cve-download.js` + tier matching `lib/cve-match.js`).
|
|
103
|
-
- `npm`/`yarn` → retire.js (`lib/retire.js`).
|
|
104
|
-
- `nuget` / `composer` / `pypi` → aucun (couverts par OSV + NVD).
|
|
105
|
-
|
|
106
|
-
### Flux d'orchestration (`fad-checker.js`)
|
|
107
|
-
|
|
108
|
-
```
|
|
109
|
-
1. detectCodecs(src) filtré par --ecosystem
|
|
110
|
-
2. resolved = Map() ; pour chaque codec actif : merge codec.collect(src,opts).deps
|
|
111
|
-
3. expansion transitive : déléguée au codec (maven → walker Maven Central ; autres → lock)
|
|
112
|
-
4. vuln :
|
|
113
|
-
a. OSV (tronc commun) groupé par codec.osvEcosystem
|
|
114
|
-
b. nativeScanners par codec (maven: CVE-index ; npm/yarn: retire.js)
|
|
115
|
-
c. NVD enrichment (agnostique, par CVE-id)
|
|
116
|
-
d. CPE refinement (agnostique)
|
|
117
|
-
5. EOL / obsolete / outdated : codec.resolveEolProduct + codec.checkRegistry
|
|
118
|
-
6. render : sections pilotées par les codecs présents
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Spécifications par codec
|
|
122
|
-
|
|
123
|
-
### maven (extrait)
|
|
124
|
-
|
|
125
|
-
`collect()` enveloppe `lib/core.js` (parse POM, résolution parent, merge multi-profils,
|
|
126
|
-
`<dependencyManagement>`, imports `scope=import`/BOM) **conservés tels quels** + walker
|
|
127
|
-
`lib/transitive.js`. WebJars : `webjarToNpm()` reste **dans le codec maven** (un WebJar
|
|
128
|
-
est un artifact Maven émettant une coord npm ; le codec maven route ces deps vers le
|
|
129
|
-
chemin npm pour EOL/registre).
|
|
130
|
-
|
|
131
|
-
| Aspect | Valeur |
|
|
132
|
-
|---|---|
|
|
133
|
-
| coordKey | `maven:groupId:artifactId` |
|
|
134
|
-
| osvEcosystem | `Maven` |
|
|
135
|
-
| nativeScanners | CVE-index cvelistV5 |
|
|
136
|
-
| registre | Maven Central Solr |
|
|
137
|
-
| obsolete | `data/known-obsolete.json` (curated) |
|
|
138
|
-
| EOL | `by_group_artifact` / `by_group_prefix` |
|
|
139
|
-
| recette | `<dependencyManagement>` + maj des deps directes |
|
|
140
|
-
|
|
141
|
-
### npm / yarn (extrait)
|
|
142
|
-
|
|
143
|
-
`collect()` enveloppe `lib/npm/parse.js` + `lib/npm/collect.js` (parsers
|
|
144
|
-
package-lock v1/2/3, yarn.lock v1) **conservés**. Deux codecs partageant `osvEcosystem=npm`
|
|
145
|
-
et le préfixe coordKey `npm:` ; `ecosystemType` distingue pour report/recette.
|
|
146
|
-
|
|
147
|
-
| Aspect | Valeur |
|
|
148
|
-
|---|---|
|
|
149
|
-
| coordKey | `npm:name` (ou `npm:@scope/name`) |
|
|
150
|
-
| osvEcosystem | `npm` |
|
|
151
|
-
| nativeScanners | retire.js |
|
|
152
|
-
| registre | npm registry (`deprecated` + `dist-tags.latest`) |
|
|
153
|
-
| EOL | `by_npm_name` / `by_npm_scope` |
|
|
154
|
-
| recette | `overrides` (npm) / `resolutions` (yarn) |
|
|
155
|
-
|
|
156
|
-
### nuget (C#/.NET) — nouveau
|
|
157
|
-
|
|
158
|
-
| Aspect | Valeur |
|
|
159
|
-
|---|---|
|
|
160
|
-
| manifestes | `packages.lock.json` (lock, résolu+transitif), `*.csproj` (`<PackageReference>`, XML via xml2js), `packages.config` (XML legacy) |
|
|
161
|
-
| CPM | `Directory.Packages.props` lu comme **table de versions** ; `PackageReference` sans `Version` résolu contre `<PackageVersion>`, sinon warning `unresolved-versions` |
|
|
162
|
-
| fallback | `.csproj` épinglé scanné ; floating (`1.*`, `[1.0,2.0)`) skip+warning |
|
|
163
|
-
| coordKey | `nuget:<name-lower>` (NuGet case-insensitive ; casse d'origine gardée pour l'affichage) |
|
|
164
|
-
| osvEcosystem | `NuGet` |
|
|
165
|
-
| registre | `api.nuget.org` registration index → version stable max + `deprecation.reasons`/`alternatePackage` |
|
|
166
|
-
| EOL | endoflife.date `dotnet` (target framework `net6.0`/`net48`…), `aspnet`, `entity-framework` via `by_nuget_name` |
|
|
167
|
-
| recette | maj `<PackageReference>` + note CPM `Directory.Packages.props` |
|
|
168
|
-
|
|
169
|
-
### composer (PHP) — nouveau
|
|
170
|
-
|
|
171
|
-
| Aspect | Valeur |
|
|
172
|
-
|---|---|
|
|
173
|
-
| manifestes | `composer.lock` (JSON, `packages[]` + `packages-dev[]`, concret+transitif) ; `composer.json` sans lock → fallback+warning |
|
|
174
|
-
| coordKey | `composer:vendor/package` (case-insensitive normalisé) |
|
|
175
|
-
| osvEcosystem | `Packagist` |
|
|
176
|
-
| registre | `repo.packagist.org/p2/{vendor}/{pkg}.json` → dernière version + champ **`abandoned`** (≈ deprecated, peut pointer un remplaçant) |
|
|
177
|
-
| EOL | endoflife.date `php`, `laravel`, `symfony`, `drupal` via `by_composer_name` |
|
|
178
|
-
| recette | `composer require vendor/pkg:^x` / bloc `composer.json` |
|
|
179
|
-
|
|
180
|
-
### pypi (Python) — nouveau
|
|
181
|
-
|
|
182
|
-
| Aspect | Valeur |
|
|
183
|
-
|---|---|
|
|
184
|
-
| manifestes | `poetry.lock` (TOML), `Pipfile.lock` (JSON), `uv.lock` / `pdm.lock` (TOML), `requirements.txt` (épinglé `==` seul) |
|
|
185
|
-
| fallback | `requirements.txt` à ranges → skip+warning ; `pyproject.toml`/`Pipfile` sans lock → fallback+warning |
|
|
186
|
-
| normalisation | PEP 503 : `Foo.Bar_baz` → `foo-bar-baz` pour clé/OSV/registre ; casse d'origine pour affichage |
|
|
187
|
-
| coordKey | `pypi:<name-pep503>` |
|
|
188
|
-
| osvEcosystem | `PyPI` |
|
|
189
|
-
| registre | `pypi.org/pypi/{name}/json` → `info.version` (latest) + détection `yanked` par version + classifier `Development Status :: 7 - Inactive` comme signal obsolete |
|
|
190
|
-
| EOL | endoflife.date `python`, `django`, `numpy`, `fastapi` via `by_pypi_name` |
|
|
191
|
-
| recette | `pip install 'pkg>=x'` / ligne `requirements.txt` / bloc selon le lock détecté |
|
|
192
|
-
|
|
193
|
-
## Données
|
|
194
|
-
|
|
195
|
-
- `data/eol-mapping.json` gagne `by_nuget_name`, `by_composer_name`, `by_pypi_name`.
|
|
196
|
-
Ce fichier ne contient **que l'association coord → nom de produit endoflife.date** ;
|
|
197
|
-
les dates de cycle/EOL sont **récupérées en ligne** (endoflife.date) et **mises en
|
|
198
|
-
cache 7 jours**. Le mapping peut donc démarrer **minimal** (dotnet/php/python + quelques
|
|
199
|
-
frameworks) et s'enrichir au fil de l'eau par simple édition JSON, sans toucher au code.
|
|
200
|
-
- `data/known-obsolete.json` reste Maven-only : les autres écosystèmes ont des champs
|
|
201
|
-
registre authoritatifs (`deprecation` NuGet, `abandoned` Packagist, `yanked` PyPI).
|
|
202
|
-
|
|
203
|
-
## CLI
|
|
204
|
-
|
|
205
|
-
- `--ecosystem` : enum → **liste** : `auto` (défaut, = `detect()` vrai) | `all` | liste
|
|
206
|
-
explicite `maven,nuget,pypi,…`.
|
|
207
|
-
- `--no-<id>` génériques : `--no-maven --no-npm --no-yarn --no-nuget --no-composer --no-pypi`.
|
|
208
|
-
- `--no-js` conservé comme **alias** de `--no-npm`+`--no-yarn` (rétro-compat).
|
|
209
|
-
- `--transitive` / `--transitive-depth` : s'appliquent au codec maven uniquement
|
|
210
|
-
(no-op documenté ailleurs ; les autres ont le transitif via lock).
|
|
211
|
-
- `--snyk` : inchangé (POM nettoyé maven).
|
|
212
|
-
|
|
213
|
-
## Report (`lib/cve-report.js`)
|
|
214
|
-
|
|
215
|
-
- Sections par écosystème (1.a, 1.b, …) **générées dynamiquement** selon les codecs
|
|
216
|
-
présents, ordre stable : `maven, npm, yarn, nuget, composer, pypi`.
|
|
217
|
-
- `RECIPE_SPECS` / `ECO_LABELS` / `ECO_MANIFEST_KIND` alimentés depuis `codec.label`,
|
|
218
|
-
`codec.recipe`, `codec.manifestNames` — fin des maps codées en dur.
|
|
219
|
-
- `formatCoord(dep)` délégué au codec — fin des `if (ecosystem === "npm")` dans le rendu.
|
|
220
|
-
|
|
221
|
-
## Tests
|
|
222
|
-
|
|
223
|
-
Objectif : les 96 tests existants restent verts + nouveaux tests.
|
|
224
|
-
|
|
225
|
-
- Fixtures : `csharp-app/` (.csproj + packages.lock.json + packages.config +
|
|
226
|
-
Directory.Packages.props), `php-app/` (composer.json + composer.lock),
|
|
227
|
-
`python-app/` (un répertoire par format : requirements.txt épinglé, poetry.lock,
|
|
228
|
-
Pipfile.lock, uv.lock).
|
|
229
|
-
- Un fichier de test par codec (`nuget.test.js`, `composer.test.js`, `pypi.test.js`) :
|
|
230
|
-
parsing, coordKey, normalisation de noms (PEP 503, case-insensitive NuGet),
|
|
231
|
-
fallback no-lockfile.
|
|
232
|
-
- Test de contrat paramétré sur `allCodecs()` : chaque codec implémente toute l'interface.
|
|
233
|
-
- Fixture `polyglot/` mêlant les 5 écosystèmes (ou extension de `monorepo-mixed`).
|
|
234
|
-
|
|
235
|
-
## Ordre d'exécution du big-bang (chaque étape laisse le pipeline fonctionnel)
|
|
236
|
-
|
|
237
|
-
1. `codec.interface.js` + `lib/codecs/index.js` (registre vide).
|
|
238
|
-
2. Généraliser le `depRecord` (`namespace`/`name`/`coordKey`/`manifestPaths`) — adapter
|
|
239
|
-
collecteurs + report + tests existants.
|
|
240
|
-
3. Extraire `maven` et `npm`/`yarn` dans des codecs (déplacement, pas réécriture) ;
|
|
241
|
-
brancher l'orchestrateur sur le registre. **→ les 96 tests repassent ici.**
|
|
242
|
-
4. Rendre `osv.js` / `nvd.js` / `cpe.js` / endoflife agnostiques (via codec).
|
|
243
|
-
5. Ajouter `composer` (lock JSON, le plus simple), puis `pypi`, puis `nuget`.
|
|
244
|
-
6. Report dynamique + CLI liste + fixtures + tests nouveaux.
|
|
245
|
-
|
|
246
|
-
## Hors périmètre (YAGNI)
|
|
247
|
-
|
|
248
|
-
- Pas de résolution de ranges/floating (cohérent lockfile-first ; ranges → warning).
|
|
249
|
-
- Pas de support yarn-berry (déjà `unsupported` aujourd'hui), pas de pnpm dans ce lot.
|
|
250
|
-
- Pas de scanner natif dédié pour nuget/composer/pypi (OSV + NVD suffisent à la parité).
|
|
251
|
-
- Pas de curation obsolete pour les nouveaux écosystèmes (champs registre authoritatifs).
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const test = require("node:test");
|
|
2
|
-
const assert = require("node:assert");
|
|
3
|
-
const { resolveActiveCodecs } = require("../lib/codecs/select");
|
|
4
|
-
|
|
5
|
-
test("resolveActiveCodecs parses comma list and 'all'", () => {
|
|
6
|
-
const all = ["maven", "npm", "nuget", "composer", "pypi"];
|
|
7
|
-
assert.deepStrictEqual(resolveActiveCodecs("maven,pypi", all, {}), ["maven", "pypi"]);
|
|
8
|
-
assert.deepStrictEqual(resolveActiveCodecs("all", all, {}), all);
|
|
9
|
-
assert.deepStrictEqual(resolveActiveCodecs("auto", all, {}), all);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test("resolveActiveCodecs honors --no-<id> flags", () => {
|
|
13
|
-
const all = ["maven", "npm", "nuget"];
|
|
14
|
-
assert.deepStrictEqual(resolveActiveCodecs("all", all, { noCodecs: ["npm"] }), ["maven", "nuget"]);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
test("--no-js aliases to npm+yarn", () => {
|
|
18
|
-
const all = ["maven", "npm", "yarn", "nuget"];
|
|
19
|
-
assert.deepStrictEqual(resolveActiveCodecs("all", all, { noJs: true }), ["maven", "nuget"]);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test("legacy 'both' maps to maven+npm/yarn only", () => {
|
|
23
|
-
const all = ["maven", "npm", "yarn", "nuget", "pypi"];
|
|
24
|
-
assert.deepStrictEqual(resolveActiveCodecs("both", all, {}), ["maven", "npm", "yarn"]);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
test("explicit list intersects with available (auto-detect)", () => {
|
|
28
|
-
const detected = ["maven"]; // only maven detected on this tree
|
|
29
|
-
assert.deepStrictEqual(resolveActiveCodecs("maven,npm", detected, {}), ["maven"]);
|
|
30
|
-
});
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Advanced edge-case + robustness tests for the composer/pypi/nuget codecs.
|
|
3
|
-
* Uses temp dirs so we can throw malformed inputs at the collectors.
|
|
4
|
-
*/
|
|
5
|
-
const test = require("node:test");
|
|
6
|
-
const assert = require("node:assert");
|
|
7
|
-
const fs = require("fs");
|
|
8
|
-
const os = require("os");
|
|
9
|
-
const path = require("path");
|
|
10
|
-
|
|
11
|
-
const composer = require("../lib/codecs/composer.codec");
|
|
12
|
-
const pypi = require("../lib/codecs/pypi.codec");
|
|
13
|
-
const nuget = require("../lib/codecs/nuget.codec");
|
|
14
|
-
const { parseRequirementsTxt, pep503 } = require("../lib/codecs/pypi/parse");
|
|
15
|
-
|
|
16
|
-
function tmp(prefix, files) {
|
|
17
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
18
|
-
for (const [name, content] of Object.entries(files)) {
|
|
19
|
-
const fp = path.join(dir, name);
|
|
20
|
-
fs.mkdirSync(path.dirname(fp), { recursive: true });
|
|
21
|
-
fs.writeFileSync(fp, content);
|
|
22
|
-
}
|
|
23
|
-
return dir;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/* ---------------- composer ---------------- */
|
|
27
|
-
|
|
28
|
-
test("composer: ignoreTest drops dev packages; deps2Exclude filters by name", async () => {
|
|
29
|
-
const dir = tmp("comp-edge-", {
|
|
30
|
-
"composer.lock": JSON.stringify({
|
|
31
|
-
packages: [{ name: "guzzlehttp/guzzle", version: "7.4.5" }, { name: "acme/private", version: "1.0.0" }],
|
|
32
|
-
"packages-dev": [{ name: "phpunit/phpunit", version: "10.0.0" }],
|
|
33
|
-
}),
|
|
34
|
-
});
|
|
35
|
-
const noDev = await composer.collect(dir, { ignoreTest: true });
|
|
36
|
-
assert.ok(!noDev.deps.has("composer:phpunit/phpunit"), "dev dep dropped with ignoreTest");
|
|
37
|
-
assert.ok(noDev.deps.has("composer:guzzlehttp/guzzle"));
|
|
38
|
-
const excl = await composer.collect(dir, { deps2Exclude: /^acme\// });
|
|
39
|
-
assert.ok(!excl.deps.has("composer:acme/private"), "excluded by regex");
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("composer: malformed composer.lock does not throw, emits parse-error warning", async () => {
|
|
43
|
-
const dir = tmp("comp-bad-", { "composer.lock": "{ not valid json ]" });
|
|
44
|
-
const { deps, warnings } = await composer.collect(dir, {});
|
|
45
|
-
assert.strictEqual(deps.size, 0);
|
|
46
|
-
assert.ok(warnings.find(w => w.type === "parse-error"));
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
/* ---------------- pypi ---------------- */
|
|
50
|
-
|
|
51
|
-
test("pypi: requirements.txt handles extras, env markers, -r/-e/comments", () => {
|
|
52
|
-
const dir = tmp("py-req-", {
|
|
53
|
-
"requirements.txt": [
|
|
54
|
-
"# header",
|
|
55
|
-
"requests[security]==2.31.0", // extras → still pinned
|
|
56
|
-
'django==4.2 ; python_version >= "3.8"', // env marker dropped
|
|
57
|
-
"flask>=2.0", // range → skip
|
|
58
|
-
"-r other.txt", // include → skip
|
|
59
|
-
"-e .", // editable → skip
|
|
60
|
-
"",
|
|
61
|
-
].join("\n"),
|
|
62
|
-
});
|
|
63
|
-
const r = parseRequirementsTxt(path.join(dir, "requirements.txt"));
|
|
64
|
-
const names = r.deps.map(d => d.name).sort();
|
|
65
|
-
assert.deepStrictEqual(names, ["django", "requests"]);
|
|
66
|
-
assert.strictEqual(r.deps.find(d => d.name === "requests").version, "2.31.0");
|
|
67
|
-
assert.strictEqual(r.skipped, 1); // only flask>=2.0 counts as a skipped requirement spec
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test("pypi: pep503 collapses mixed separators and casing", () => {
|
|
71
|
-
assert.strictEqual(pep503("Jinja2"), "jinja2");
|
|
72
|
-
assert.strictEqual(pep503("ruamel.yaml"), "ruamel-yaml");
|
|
73
|
-
assert.strictEqual(pep503("backports.zoneinfo"), "backports-zoneinfo");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("pypi: lockfile wins over requirements.txt in the same directory", async () => {
|
|
77
|
-
const dir = tmp("py-prec-", {
|
|
78
|
-
"poetry.lock": '[[package]]\nname = "requests"\nversion = "2.31.0"\n',
|
|
79
|
-
"requirements.txt": "requests==1.0.0\n",
|
|
80
|
-
});
|
|
81
|
-
const { deps } = await pypi.collect(dir, {});
|
|
82
|
-
assert.strictEqual(deps.get("pypi:requests").version, "2.31.0", "poetry.lock should win over requirements.txt");
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("pypi: malformed poetry.lock does not throw", async () => {
|
|
86
|
-
const dir = tmp("py-bad-", { "poetry.lock": "this is = not [ valid toml" });
|
|
87
|
-
const { deps, warnings } = await pypi.collect(dir, {});
|
|
88
|
-
assert.strictEqual(deps.size, 0);
|
|
89
|
-
assert.ok(warnings.find(w => w.type === "parse-error"));
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
/* ---------------- nuget ---------------- */
|
|
93
|
-
|
|
94
|
-
test("nuget: packages.lock.json dedups the same id across target frameworks", async () => {
|
|
95
|
-
const dir = tmp("nuget-multi-tfm-", {
|
|
96
|
-
"packages.lock.json": JSON.stringify({
|
|
97
|
-
version: 1,
|
|
98
|
-
dependencies: {
|
|
99
|
-
"net6.0": { "Newtonsoft.Json": { type: "Direct", resolved: "13.0.1" } },
|
|
100
|
-
"net8.0": { "Newtonsoft.Json": { type: "Direct", resolved: "13.0.1" } },
|
|
101
|
-
},
|
|
102
|
-
}),
|
|
103
|
-
});
|
|
104
|
-
const { deps } = await nuget.collect(dir, {});
|
|
105
|
-
assert.strictEqual(deps.size, 1, "same id+version across TFMs collapses to one entry");
|
|
106
|
-
assert.ok(deps.has("nuget:newtonsoft.json"));
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("nuget: Version as a child element is read", async () => {
|
|
110
|
-
const dir = tmp("nuget-child-", {
|
|
111
|
-
"app.csproj": `<Project Sdk="Microsoft.NET.Sdk"><ItemGroup><PackageReference Include="Serilog"><Version>2.12.0</Version></PackageReference></ItemGroup></Project>`,
|
|
112
|
-
});
|
|
113
|
-
const { deps } = await nuget.collect(dir, {});
|
|
114
|
-
assert.strictEqual(deps.get("nuget:serilog").version, "2.12.0");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("nuget: packages.config collected via codec", async () => {
|
|
118
|
-
const dir = tmp("nuget-cfg-", {
|
|
119
|
-
"packages.config": `<?xml version="1.0"?><packages><package id="EntityFramework" version="6.4.4" /></packages>`,
|
|
120
|
-
});
|
|
121
|
-
const { deps } = await nuget.collect(dir, {});
|
|
122
|
-
assert.ok(deps.has("nuget:entityframework"));
|
|
123
|
-
assert.strictEqual(deps.get("nuget:entityframework").name, "EntityFramework");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("nuget: malformed packages.lock.json does not throw", async () => {
|
|
127
|
-
const dir = tmp("nuget-bad-", { "packages.lock.json": "{ broken" });
|
|
128
|
-
const { deps, warnings } = await nuget.collect(dir, {});
|
|
129
|
-
assert.strictEqual(deps.size, 0);
|
|
130
|
-
assert.ok(warnings.find(w => w.type === "parse-error"));
|
|
131
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-ecosystem integration tests — exercise all codecs together the way the
|
|
3
|
-
* orchestrator does: detect → collect → merge into one Map → shared services.
|
|
4
|
-
*/
|
|
5
|
-
const test = require("node:test");
|
|
6
|
-
const assert = require("node:assert");
|
|
7
|
-
const path = require("path");
|
|
8
|
-
const { allCodecs, getCodec, detectCodecs } = require("../lib/codecs");
|
|
9
|
-
const { assertCodecShape } = require("../lib/codecs/codec.interface");
|
|
10
|
-
const { osvEcosystemFor, osvPkgName } = require("../lib/osv");
|
|
11
|
-
const { findEolProduct } = require("../lib/outdated");
|
|
12
|
-
const { generateHtmlReport } = require("../lib/cve-report");
|
|
13
|
-
|
|
14
|
-
const POLY = path.join(__dirname, "fixtures", "polyglot");
|
|
15
|
-
|
|
16
|
-
// Collect every active codec into one Map, exactly like fad-checker.js main().
|
|
17
|
-
async function collectAll(dir) {
|
|
18
|
-
const resolved = new Map();
|
|
19
|
-
const warnings = [];
|
|
20
|
-
for (const id of detectCodecs(dir).map(c => c.id)) {
|
|
21
|
-
if (id === "yarn") continue;
|
|
22
|
-
const { deps, warnings: w } = await getCodec(id).collect(dir, {});
|
|
23
|
-
for (const [k, v] of deps) resolved.set(k, v);
|
|
24
|
-
if (w) warnings.push(...w);
|
|
25
|
-
}
|
|
26
|
-
return { resolved, warnings };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test("every registered codec satisfies the interface contract", () => {
|
|
30
|
-
for (const c of allCodecs()) assertCodecShape(c);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("detectCodecs finds all five ecosystems in the polyglot tree", () => {
|
|
34
|
-
const ids = detectCodecs(POLY).map(c => c.id).sort();
|
|
35
|
-
assert.deepStrictEqual(ids, ["composer", "maven", "npm", "nuget", "pypi"]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("collecting all codecs merges into one Map with NO coordKey collisions", async () => {
|
|
39
|
-
const { resolved } = await collectAll(POLY);
|
|
40
|
-
// Every key unique (Map guarantees) AND every record's coordKey === its key.
|
|
41
|
-
for (const [k, d] of resolved) {
|
|
42
|
-
assert.strictEqual(d.coordKey, k, `coordKey mismatch for ${k}`);
|
|
43
|
-
assert.ok(d.ecosystem, `missing ecosystem for ${k}`);
|
|
44
|
-
assert.ok(d.name, `missing name for ${k}`);
|
|
45
|
-
}
|
|
46
|
-
// All five ecosystems represented.
|
|
47
|
-
const ecos = new Set([...resolved.values()].map(d => d.ecosystem));
|
|
48
|
-
for (const e of ["maven", "npm", "composer", "pypi", "nuget"]) assert.ok(ecos.has(e), `missing ${e} deps`);
|
|
49
|
-
// Spot-check one coord per ecosystem.
|
|
50
|
-
assert.ok(resolved.has("org.apache.logging.log4j:log4j-core"));
|
|
51
|
-
assert.ok(resolved.has("npm:lodash"));
|
|
52
|
-
assert.ok(resolved.has("composer:guzzlehttp/guzzle"));
|
|
53
|
-
assert.ok(resolved.has("pypi:requests"));
|
|
54
|
-
assert.ok(resolved.has("nuget:newtonsoft.json"));
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
test("coordKeys from different ecosystems never collide even for same bare name", () => {
|
|
58
|
-
const { coordKeyFor } = require("../lib/dep-record");
|
|
59
|
-
const keys = [
|
|
60
|
-
coordKeyFor("maven", "org.x", "thing"),
|
|
61
|
-
coordKeyFor("npm", "", "thing"),
|
|
62
|
-
coordKeyFor("composer", "vendor", "thing"),
|
|
63
|
-
coordKeyFor("pypi", "", "thing"),
|
|
64
|
-
coordKeyFor("nuget", "", "thing"),
|
|
65
|
-
];
|
|
66
|
-
assert.strictEqual(new Set(keys).size, keys.length, `collision among ${keys.join(", ")}`);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("osv ecosystem + package name are correct for every codec", () => {
|
|
70
|
-
const cases = [
|
|
71
|
-
{ dep: { ecosystem: "maven", namespace: "org.apache", name: "log4j-core", groupId: "org.apache", artifactId: "log4j-core" }, eco: "Maven", pkg: "org.apache:log4j-core" },
|
|
72
|
-
{ dep: { ecosystem: "npm", namespace: "", name: "lodash", artifactId: "lodash" }, eco: "npm", pkg: "lodash" },
|
|
73
|
-
{ dep: { ecosystem: "composer", namespace: "guzzlehttp", name: "guzzle" }, eco: "Packagist", pkg: "guzzlehttp/guzzle" },
|
|
74
|
-
{ dep: { ecosystem: "pypi", namespace: "", name: "requests" }, eco: "PyPI", pkg: "requests" },
|
|
75
|
-
{ dep: { ecosystem: "nuget", namespace: "", name: "Newtonsoft.Json" }, eco: "NuGet", pkg: "Newtonsoft.Json" },
|
|
76
|
-
];
|
|
77
|
-
for (const c of cases) {
|
|
78
|
-
assert.strictEqual(osvEcosystemFor(c.dep), c.eco, `osv eco for ${c.dep.ecosystem}`);
|
|
79
|
-
assert.strictEqual(osvPkgName(c.dep), c.pkg, `osv pkg for ${c.dep.ecosystem}`);
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("findEolProduct dispatches per ecosystem to the right product", () => {
|
|
84
|
-
assert.strictEqual(findEolProduct({ ecosystem: "maven", groupId: "org.apache.logging.log4j", artifactId: "log4j-core", namespace: "org.apache.logging.log4j", name: "log4j-core" })?.product, "log4j");
|
|
85
|
-
assert.strictEqual(findEolProduct({ ecosystem: "npm", artifactId: "angular", name: "angular" })?.product, "angularjs");
|
|
86
|
-
assert.strictEqual(findEolProduct({ ecosystem: "composer", namespace: "symfony", name: "console" })?.product, "symfony");
|
|
87
|
-
assert.strictEqual(findEolProduct({ ecosystem: "pypi", name: "django" })?.product, "django");
|
|
88
|
-
assert.strictEqual(findEolProduct({ ecosystem: "nuget", name: "Microsoft.EntityFrameworkCore" })?.product, "efcore");
|
|
89
|
-
// unknown → null
|
|
90
|
-
assert.strictEqual(findEolProduct({ ecosystem: "pypi", name: "totally-unknown-pkg" }), null);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("generateHtmlReport renders findings from every ecosystem without throwing", async () => {
|
|
94
|
-
const { resolved } = await collectAll(POLY);
|
|
95
|
-
const deps = [...resolved.values()];
|
|
96
|
-
const mk = d => ({ dep: d, cve: { id: `CVE-TEST-${d.ecosystem}`, severity: "HIGH", score: 7.5, description: `vuln in ${d.name}` }, source: "osv", confidence: "exact" });
|
|
97
|
-
const html = generateHtmlReport({
|
|
98
|
-
cveMatches: deps.map(mk),
|
|
99
|
-
eolResults: [], obsoleteResults: [], outdatedResults: [],
|
|
100
|
-
resolvedDeps: resolved,
|
|
101
|
-
projectInfo: { name: "polyglot", generatedAt: "2026-05-29", toolVersion: "test" },
|
|
102
|
-
});
|
|
103
|
-
assert.ok(html.startsWith("<!doctype html>"));
|
|
104
|
-
// Each ecosystem's section label should appear.
|
|
105
|
-
for (const label of ["Maven", "npm", "Composer", "PyPI", "NuGet"]) {
|
|
106
|
-
assert.ok(html.includes(label), `report should mention ${label}`);
|
|
107
|
-
}
|
|
108
|
-
// Ecosystem-tagged coordinates render (non-maven get a prefix).
|
|
109
|
-
assert.ok(html.includes("composer:") || html.includes("guzzlehttp/guzzle"));
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
test("every codec recipe renders a non-empty snippet from a sample finding", () => {
|
|
113
|
-
const sample = [{ groupId: "g", artifactId: "pkg", fixVersion: "1.2.3" }];
|
|
114
|
-
for (const c of allCodecs()) {
|
|
115
|
-
const out = c.recipe.snippet(sample);
|
|
116
|
-
assert.strictEqual(typeof out, "string");
|
|
117
|
-
assert.ok(out.length > 0, `${c.id} recipe snippet empty`);
|
|
118
|
-
}
|
|
119
|
-
});
|
package/test/codecs.test.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
const test = require("node:test");
|
|
2
|
-
const assert = require("node:assert");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const { assertCodecShape } = require("../lib/codecs/codec.interface");
|
|
5
|
-
|
|
6
|
-
const COMPLETE_STUB = {
|
|
7
|
-
id: "x", label: "X", osvEcosystem: "npm",
|
|
8
|
-
manifestNames: ["x.json"],
|
|
9
|
-
detect: () => false,
|
|
10
|
-
collect: async () => ({ deps: new Map(), warnings: [] }),
|
|
11
|
-
coordKey: d => `x:${d.name}`,
|
|
12
|
-
formatCoord: d => d.name,
|
|
13
|
-
osvPackageName: d => d.name,
|
|
14
|
-
checkRegistry: async () => ({ outdated: [], deprecated: [] }),
|
|
15
|
-
resolveEolProduct: () => null,
|
|
16
|
-
recipe: { label: "X", pinSection: "", pinIntro: () => "", snippet: () => "", directSection: "" },
|
|
17
|
-
nativeScanners: [],
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
test("assertCodecShape accepts a complete codec stub", () => {
|
|
21
|
-
assert.doesNotThrow(() => assertCodecShape(COMPLETE_STUB));
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("assertCodecShape rejects a codec missing a required method", () => {
|
|
25
|
-
assert.throws(() => assertCodecShape({ id: "y" }), /missing|y/i);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const maven = require("../lib/codecs/maven.codec");
|
|
29
|
-
|
|
30
|
-
test("maven codec detects the simple fixture and collects deps with bare g:a coordKeys", async () => {
|
|
31
|
-
const dir = path.join(__dirname, "fixtures", "simple");
|
|
32
|
-
assert.strictEqual(maven.detect(dir), true);
|
|
33
|
-
const { deps } = await maven.collect(dir, {});
|
|
34
|
-
assert.ok(deps.size > 0);
|
|
35
|
-
for (const [k, d] of deps) {
|
|
36
|
-
assert.strictEqual(d.ecosystem, "maven");
|
|
37
|
-
assert.strictEqual(d.coordKey, k);
|
|
38
|
-
assert.ok(!k.startsWith("npm:") && !k.startsWith("nuget:"), `maven key ${k} must stay bare`);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const npm = require("../lib/codecs/npm.codec");
|
|
43
|
-
|
|
44
|
-
test("npm codec collects from monorepo-mixed with npm: coordKeys", async () => {
|
|
45
|
-
const dir = path.join(__dirname, "fixtures", "monorepo-mixed");
|
|
46
|
-
assert.strictEqual(npm.detect(dir), true);
|
|
47
|
-
const { deps } = await npm.collect(dir, {});
|
|
48
|
-
assert.ok(deps.size > 0);
|
|
49
|
-
for (const [k, d] of deps) {
|
|
50
|
-
assert.ok(k.startsWith("npm:"), `key ${k} should be npm-namespaced`);
|
|
51
|
-
assert.strictEqual(d.ecosystem, "npm");
|
|
52
|
-
assert.strictEqual(d.coordKey, k);
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("yarn codec is a no-op collector (npm codec does the JS scan)", async () => {
|
|
57
|
-
const yarn = require("../lib/codecs/yarn.codec");
|
|
58
|
-
assert.strictEqual(yarn.id, "yarn");
|
|
59
|
-
const { deps } = await yarn.collect("/whatever", {});
|
|
60
|
-
assert.strictEqual(deps.size, 0);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const { getCodec, allCodecs, detectCodecs } = require("../lib/codecs");
|
|
64
|
-
|
|
65
|
-
test("registry exposes maven/npm/yarn and validates their shape", () => {
|
|
66
|
-
const ids = allCodecs().map(c => c.id).sort();
|
|
67
|
-
assert.deepStrictEqual(ids, ["composer", "maven", "npm", "nuget", "pypi", "yarn"]);
|
|
68
|
-
for (const c of allCodecs()) assertCodecShape(c);
|
|
69
|
-
assert.strictEqual(getCodec("maven").id, "maven");
|
|
70
|
-
assert.strictEqual(getCodec("nope"), null);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test("detectCodecs finds maven+npm on monorepo-mixed, not yarn duplicate", () => {
|
|
74
|
-
const dir = path.join(__dirname, "fixtures", "monorepo-mixed");
|
|
75
|
-
const detected = detectCodecs(dir).map(c => c.id);
|
|
76
|
-
assert.ok(detected.includes("maven"));
|
|
77
|
-
assert.ok(detected.includes("npm"));
|
|
78
|
-
assert.ok(!detected.includes("yarn"));
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const recipes = require("../lib/codecs/recipes");
|
|
82
|
-
|
|
83
|
-
test("each codec recipe exposes label + snippet function", () => {
|
|
84
|
-
for (const c of allCodecs()) {
|
|
85
|
-
assert.strictEqual(typeof c.recipe.label, "string");
|
|
86
|
-
assert.strictEqual(typeof c.recipe.snippet, "function");
|
|
87
|
-
}
|
|
88
|
-
// snippet output matches the legacy format
|
|
89
|
-
const xml = recipes.maven.snippet([{ groupId: "g", artifactId: "a", fixVersion: "1.0" }]);
|
|
90
|
-
assert.match(xml, /<dependencyManagement>/);
|
|
91
|
-
assert.match(xml, /<artifactId>a<\/artifactId>/);
|
|
92
|
-
});
|