fhirsmith 0.14.2 → 0.15.1
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 +56 -1
- package/config-template.json +10 -0
- package/library/changelog.js +57 -0
- package/library/npm-audit.js +62 -9
- package/package.json +2 -2
- package/packages/npm-check-page.js +108 -0
- package/packages/npm-check.js +450 -0
- package/packages/openapi.yaml +158 -5
- package/packages/package-crawler.js +203 -20
- package/packages/packages.js +250 -8
- package/packages/readme.md +55 -2
- package/packages/utilities/dedupe-versions.js +242 -0
- package/registry/crawler.js +17 -0
- package/registry/fhirsmith-releases.js +3 -6
- package/registry/model.js +3 -0
- package/registry/registry.js +28 -3
- package/translations/Messages.properties +3 -0
- package/tx/README.md +20 -4
- package/tx/cm/cm-api.js +25 -10
- package/tx/cm/cm-database.js +0 -4
- package/tx/cm/cm-package.js +53 -14
- package/tx/cs/cs-api.js +6 -1
- package/tx/cs/cs-icd11.js +23 -6
- package/tx/cs/cs-provider-api.js +22 -7
- package/tx/cs/cs-provider-list.js +10 -9
- package/tx/cs/icd11-expressions.js +5 -6
- package/tx/importers/icd11-schema.md +1 -0
- package/tx/importers/import-icd11.js +39 -0
- package/tx/library/resource-ids.js +107 -0
- package/tx/library.js +96 -24
- package/tx/ocl/cm-ocl.cjs +23 -15
- package/tx/ocl/cs-ocl.cjs +9 -10
- package/tx/ocl/vs-ocl.cjs +17 -17
- package/tx/provider.js +80 -13
- package/tx/tx.fhir.org.yml +17 -9
- package/tx/tx.js +7 -1
- package/tx/vs/vs-api.js +25 -10
- package/tx/vs/vs-database.js +0 -4
- package/tx/vs/vs-package.js +48 -31
- package/tx/vs/vs-vsac.js +20 -5
- package/tx/workers/lookup.js +7 -4
- package/tx/workers/metadata.js +2 -1
- package/tx/workers/read.js +3 -18
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,61 @@ All notable changes to Health Intersections FHIRsmith will be documented in this
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.15.1] - 2026-10-15
|
|
9
|
+
|
|
10
|
+
### Security
|
|
11
|
+
|
|
12
|
+
- Packages: the SSRF guard on the crawler's outbound requests could be bypassed with an IP address in the URL (`http://127.0.0.1/`, `http://169.254.169.254/`, `http://[::1]/`), because Node makes no DNS lookup for an IP address and the guard only ran there. IP addresses are now checked too
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Packages: `/packages/npm-check` checks any registry against the npm registry protocol, the way npm clients use it (search by name, package document, version document, tarball), and reports each check with the reason for any failure. In a browser it is a form; otherwise it returns the report as JSON. The checks also run once a day against registry.npmjs.org, and the page says if any fail there. Requests are limited in time and size, may not go to non-public addresses other than loopback (so a server can check itself), and are rate limited per client. `npmCheck` in the packages config sets the limits; `"enabled": false` turns it off
|
|
17
|
+
- Packages: npm-protocol test suites that run the same checks against registry.npmjs.org (the control: if npm changes its behaviour, this is where it shows), and against the packages module with known content
|
|
18
|
+
- Packages: the package document has npm's `time` (each version's publication date, plus `created` and `modified`)
|
|
19
|
+
- Packages: `latest` can be used in place of a version (`/packages/{id}/latest`)
|
|
20
|
+
- Packages: `packages/dedupe-versions.js`, a one-off repair for an existing database: removes duplicate id#version rows, rewrites mirror files that don't match the database, and adds the unique index. It needs nothing but Node 24; see Repairing an older database in packages/readme.md
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Packages: `GET /packages/{id}/{version}` returns npm's version document (JSON) when the request's `Accept` asks for `application/json` (or `application/vnd.npm.install-v1+json`), and the tarball otherwise. On the npm registry this URL is always the version document, and npm clients and tools that treat this server as an npm registry (including the HL7 Java tools, when they are configured with a server of npm type) ask for it with `Accept: application/json` and then download from `dist.tarball`. But FHIR clients have always downloaded the tarball from this URL - the Java tools without sending an `Accept` header at all, browsers and curl with ones that don't ask for JSON - so the tarball stays the default, and only a request that explicitly asks for JSON gets the document. The risk is a client that downloads the tarball from here while asking for JSON (typically a generic HTTP wrapper that sets `Accept: application/json` on every request). For those, and as a guarantee for the Java tools, a request with a `FHIR-Package-Download` header (any value; the Java PackageClient sends `tarball`) always gets the tarball, whatever its `Accept`. The response carries `Vary: Accept, FHIR-Package-Download` so caches keep the two apart
|
|
25
|
+
- Packages: `/packages/-/v1/search` puts the package whose id is the whole search text first, as npm does. `/packages/catalog` is unchanged
|
|
26
|
+
- Packages: each id#version is stored once. Simplifier republishes packages that other feeds publish directly, so its copy is a secondary echo: a version already held is not fetched from Simplifier, and a version held only from Simplifier is replaced when it arrives directly from its publisher's feed. A second direct copy from a different GUID is logged as an error and ignored. The database enforces this with a unique index on (Id, Version); on an existing database the server logs a warning until `dedupe-versions.js` has been run
|
|
27
|
+
- Packages: package downloads keep the bytes the server sent: a `.tgz` labelled `Content-Encoding: gzip` is no longer unzipped by the HTTP client
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- Packages: the tarball served for some versions didn't match the shasum in the package document (46 of 6961 versions on packages2.fhir.org), so npm clients would reject them. Two causes: the same id#version stored twice from different feeds (the document showed one copy's hash, the mirror held the other), and a tarball that failed validation (e.g. a draft flagged `notForPublication`) overwriting the mirror file before it was rejected. The mirror file is now written only after the package has been validated and stored
|
|
32
|
+
- Packages: packages from feeds that label `.tgz` files `Content-Encoding: x-gzip` (e.g. doc.mednet.swiss) failed with "incorrect header check"
|
|
33
|
+
- Packages: the search, catalog and updates log lines showed `[object Object]` instead of the query
|
|
34
|
+
- Packages: the crawler log showed `??` for an item skipped because another feed had it in the same crawl
|
|
35
|
+
|
|
36
|
+
### Tx Conformance Statement
|
|
37
|
+
|
|
38
|
+
FHIRsmith v0.15.1 passed all 3630 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed+mimetypes+icd-11+closure, tests v1.9.6, runner v7.0.1)
|
|
39
|
+
|
|
40
|
+
## [0.15.0] - 2026-10-08
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- Library YAML: a source can give the id prefix for the resources it loads (`npm:hl7.terminology=tho`), so their ids stay the same when sources are added, removed or reordered. Sources without one are numbered, as before. See Resource Ids in tx/README.md
|
|
45
|
+
- `CapabilityStatement.software.releaseDate` (and the TerminologyCapabilities equivalent) is the date the running version was released, taken from its heading in CHANGELOG.md, which is now included in the Docker image. A snapshot reports the time the server started
|
|
46
|
+
- Registry: the crawler records each server's `software.releaseDate`. The Software page uses it to date software other than FHIRsmith, and FHIRsmith releases (including development builds) that the release list doesn't know about yet
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **Breaking - resource ids:** every CodeSystem, ValueSet and ConceptMap is served with an id in the id space of its source - `prefix-id`, e.g. `CodeSystem/tho-v3-ActCode` - and the FHIR core package each endpoint loads uses `core` (`CodeSystem/core-administrative-gender`). An unprefixed id is not found by read or by any `[type]/[id]/$operation`. Library CodeSystems used to keep the ids they had in their packages (ValueSets and ConceptMaps were already numbered), and so could clash with the endpoint's core CodeSystems
|
|
51
|
+
- THO overrides the FHIR core packages the same way the Java validator does: where hl7.terminology has a url, the core package's CodeSystems, ValueSets and ConceptMaps with that url are not loaded at all. Older core versions used to stay available by `url|version`, so the server reported versions the validator doesn't know (e.g. v3-MaritalStatus `2018-08-12`). The exception is the R4 v2 tables 0006, 0360 and 0391, which R4 core has in two versions each with the version in the url: those versions stay available, and THO's is the default
|
|
52
|
+
- `software.releaseDate` is no longer the time of the request
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- Search: two CodeSystems with the same id from different packages (e.g. v3-MaritalStatus from hl7.fhir.r4.core and hl7.terminology) linked to the same resource, and a read of that id always returned the core one
|
|
57
|
+
- On the R4 endpoint, a ValueSet asked for without a version (e.g. `http://terminology.hl7.org/ValueSet/v3-MaritalStatus`) was the core package's copy rather than hl7.terminology's
|
|
58
|
+
|
|
59
|
+
### Tx Conformance Statement
|
|
60
|
+
|
|
61
|
+
FHIRsmith v0.15.0 passed all 3585 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed+mimetypes+icd-11+closure, tests v1.9.6, runner v7.0.0)
|
|
62
|
+
|
|
8
63
|
## [0.14.2] - 2026-10-06
|
|
9
64
|
|
|
10
65
|
### Security
|
|
@@ -596,7 +651,7 @@ FHIRsmith passed all 2729 HL7 terminology service tests (modes tx.fhir.org+omop+
|
|
|
596
651
|
|
|
597
652
|
FHIRsmith passed all 2729 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed, tests v1.9.2, runner v6.9.12)
|
|
598
653
|
|
|
599
|
-
## [v0.11.0] - 2026-
|
|
654
|
+
## [v0.11.0] - 2026-07-30
|
|
600
655
|
|
|
601
656
|
### Added
|
|
602
657
|
|
package/config-template.json
CHANGED
|
@@ -74,6 +74,16 @@
|
|
|
74
74
|
"crawler": {
|
|
75
75
|
"enabled": true,
|
|
76
76
|
"schedule": "0 * * * *"
|
|
77
|
+
},
|
|
78
|
+
// /packages/npm-check: checks any registry against the npm registry protocol. On by
|
|
79
|
+
// default; the other settings are the defaults. "control": false stops the daily
|
|
80
|
+
// check against the npm registry itself.
|
|
81
|
+
"npmCheck": {
|
|
82
|
+
"enabled": true,
|
|
83
|
+
"maxConcurrentRuns": 2,
|
|
84
|
+
"runsPerWindow": 10,
|
|
85
|
+
"requestTimeout": 30000,
|
|
86
|
+
"maxDownloadMB": 50
|
|
77
87
|
}
|
|
78
88
|
},
|
|
79
89
|
"registry": {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// library/changelog.js
|
|
2
|
+
// Release dates from CHANGELOG.md. A release is dated in its heading when it is made -
|
|
3
|
+
// "## [0.14.2] - 2026-10-06" (or "## [v0.13.4] - ...") - so the changelog that ships with
|
|
4
|
+
// the code is where a server finds out when its own version was released. A snapshot's
|
|
5
|
+
// heading has no date yet, so it has no release date.
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const DEFAULT_PATH = path.join(__dirname, '..', 'CHANGELOG.md');
|
|
11
|
+
const HEADING = /^##\s*\[v?(\d+\.\d+\.\d+)\]\s*-\s*(\d{4}-\d{2}-\d{2})\s*$/gm;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} [changelogPath]
|
|
15
|
+
* @returns {Map<string, string>} version (no leading v) -> release date (YYYY-MM-DD).
|
|
16
|
+
* Throws if the file can't be read.
|
|
17
|
+
*/
|
|
18
|
+
function readReleaseDates(changelogPath = DEFAULT_PATH) {
|
|
19
|
+
const text = fs.readFileSync(changelogPath, 'utf8');
|
|
20
|
+
const dates = new Map();
|
|
21
|
+
let m;
|
|
22
|
+
HEADING.lastIndex = 0;
|
|
23
|
+
while ((m = HEADING.exec(text)) !== null) {
|
|
24
|
+
if (!dates.has(m[1])) {
|
|
25
|
+
dates.set(m[1], m[2]);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return dates;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The release date of a version, or null if it isn't a dated release (a snapshot, or no
|
|
33
|
+
* changelog to read)
|
|
34
|
+
*/
|
|
35
|
+
function releaseDateOf(version, changelogPath = DEFAULT_PATH) {
|
|
36
|
+
try {
|
|
37
|
+
return readReleaseDates(changelogPath).get(String(version || '').replace(/^v/, '')) || null;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The release date a server reports for its version: the date of the release, or, for a
|
|
45
|
+
* version that hasn't been released (a snapshot), the time the server started - which is
|
|
46
|
+
* when that build went into service.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} version
|
|
49
|
+
* @param {Date} started - when the server started
|
|
50
|
+
* @param {string} [changelogPath]
|
|
51
|
+
* @returns {string} YYYY-MM-DD for a release, an ISO dateTime for a snapshot
|
|
52
|
+
*/
|
|
53
|
+
function reportedReleaseDate(version, started, changelogPath = DEFAULT_PATH) {
|
|
54
|
+
return releaseDateOf(version, changelogPath) || started.toISOString();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { readReleaseDates, releaseDateOf, reportedReleaseDate };
|
package/library/npm-audit.js
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
// The package list comes from node_modules/.package-lock.json (what is actually
|
|
7
7
|
// installed), falling back to package-lock.json. Dev dependencies are left out, and
|
|
8
8
|
// fhirsmith itself is included, so an advisory published against fhirsmith shows up too.
|
|
9
|
+
// A server installed with `npm install -g fhirsmith` has neither - npm doesn't publish the
|
|
10
|
+
// lock file, and keeps no hidden one for a global package - so then the package list is read
|
|
11
|
+
// from the package.json files in node_modules (a published install has no dev dependencies).
|
|
9
12
|
//
|
|
10
13
|
// Config (all optional), top level of config.json:
|
|
11
14
|
// "npmAudit": { "enabled": true, "intervalHours": 24 }
|
|
@@ -67,9 +70,6 @@ class NpmAudit {
|
|
|
67
70
|
// try the next one
|
|
68
71
|
}
|
|
69
72
|
}
|
|
70
|
-
if (!lock || !lock.packages) {
|
|
71
|
-
throw new Error('No package-lock.json found to audit');
|
|
72
|
-
}
|
|
73
73
|
const found = new Map();
|
|
74
74
|
const add = (name, version) => {
|
|
75
75
|
if (!found.has(name)) {
|
|
@@ -77,13 +77,17 @@ class NpmAudit {
|
|
|
77
77
|
}
|
|
78
78
|
found.get(name).add(version);
|
|
79
79
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
if (lock && lock.packages) {
|
|
81
|
+
for (const [key, info] of Object.entries(lock.packages)) {
|
|
82
|
+
if (!key || !info || info.dev || info.link || !info.version) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const marker = 'node_modules/';
|
|
86
|
+
const name = info.name || key.substring(key.lastIndexOf(marker) + marker.length);
|
|
87
|
+
add(name, info.version);
|
|
83
88
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
add(name, info.version);
|
|
89
|
+
} else if (!this.scanInstalled(path.join(this.appDir, 'node_modules'), add)) {
|
|
90
|
+
throw new Error('No package-lock.json or node_modules found to audit');
|
|
87
91
|
}
|
|
88
92
|
try {
|
|
89
93
|
const pkg = JSON.parse(fs.readFileSync(path.join(this.appDir, 'package.json'), 'utf8'));
|
|
@@ -100,6 +104,55 @@ class NpmAudit {
|
|
|
100
104
|
return result;
|
|
101
105
|
}
|
|
102
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Read the installed packages from the package.json files under a node_modules folder,
|
|
109
|
+
* including scoped packages and the node_modules nested inside packages. Linked packages
|
|
110
|
+
* (symlinks) are left out, as they are from a lock file.
|
|
111
|
+
*
|
|
112
|
+
* @returns {boolean} false if there is no node_modules folder
|
|
113
|
+
*/
|
|
114
|
+
scanInstalled(dir, add) {
|
|
115
|
+
let entries;
|
|
116
|
+
try {
|
|
117
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const dirs = [];
|
|
126
|
+
if (entry.name.startsWith('@')) {
|
|
127
|
+
let scoped = [];
|
|
128
|
+
try {
|
|
129
|
+
scoped = fs.readdirSync(path.join(dir, entry.name), { withFileTypes: true });
|
|
130
|
+
} catch (e) {
|
|
131
|
+
// unreadable scope folder - nothing in it
|
|
132
|
+
}
|
|
133
|
+
for (const s of scoped) {
|
|
134
|
+
if (s.isDirectory()) {
|
|
135
|
+
dirs.push(path.join(dir, entry.name, s.name));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
dirs.push(path.join(dir, entry.name));
|
|
140
|
+
}
|
|
141
|
+
for (const pkgDir of dirs) {
|
|
142
|
+
try {
|
|
143
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
|
|
144
|
+
if (pkg.name && pkg.version) {
|
|
145
|
+
add(pkg.name, pkg.version);
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
// not a package
|
|
149
|
+
}
|
|
150
|
+
this.scanInstalled(path.join(pkgDir, 'node_modules'), add);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
103
156
|
async run() {
|
|
104
157
|
if (this.running) {
|
|
105
158
|
return;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fhirsmith",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"txVersion": "1.9.
|
|
3
|
+
"version": "0.15.1",
|
|
4
|
+
"txVersion": "1.9.6",
|
|
5
5
|
"description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
|
|
6
6
|
"main": "server.js",
|
|
7
7
|
"engines": {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
|
|
3
|
+
//
|
|
4
|
+
// Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
|
|
5
|
+
//
|
|
6
|
+
|
|
7
|
+
// HTML for /packages/npm-check: the form, and the report of a run (see npm-check.js)
|
|
8
|
+
|
|
9
|
+
const escape = require('escape-html');
|
|
10
|
+
|
|
11
|
+
const PASS = '<span style="color: #198754; font-weight: bold">✓</span>';
|
|
12
|
+
const FAIL = '<span style="color: #dc3545; font-weight: bold">✗</span>';
|
|
13
|
+
|
|
14
|
+
function when(iso) {
|
|
15
|
+
return escape(new Date(iso).toUTCString());
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function form(values) {
|
|
19
|
+
let html = '<form method="get" action="/packages/npm-check" class="mb-4">';
|
|
20
|
+
html += '<table class="grid">';
|
|
21
|
+
html += `<tr><td><label for="registry">Registry URL</label></td><td><input type="url" id="registry" name="registry" size="60" required maxlength="500" placeholder="https://registry.example.org/packages" value="${escape(values.registry || '')}"></td></tr>`;
|
|
22
|
+
html += `<tr><td><label for="package">Package</label></td><td><input type="text" id="package" name="package" size="40" required maxlength="100" value="${escape(values.package || 'hl7.fhir.r4.core')}"></td></tr>`;
|
|
23
|
+
html += `<tr><td><label for="version">Version</label></td><td><input type="text" id="version" name="version" size="20" required maxlength="50" value="${escape(values.version || '4.0.1')}"></td></tr>`;
|
|
24
|
+
html += '</table>';
|
|
25
|
+
html += '<p class="mt-2"><button type="submit" class="btn btn-primary">Run the checks</button> ';
|
|
26
|
+
html += '<small>A run makes about ten requests to the registry, including downloading the package\'s tarball, and can take up to a minute.</small></p>';
|
|
27
|
+
html += '</form>';
|
|
28
|
+
return html;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function controlSummary(control, controlReport) {
|
|
32
|
+
if (!control) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
const what = `${escape(control.registry)} (${escape(control.pkg)} ${escape(control.version)})`;
|
|
36
|
+
if (!controlReport) {
|
|
37
|
+
return `<p><small>The checks are verified daily against the npm registry itself, ${what}. That hasn't run yet; reload this page in a minute to see it.</small></p>`;
|
|
38
|
+
}
|
|
39
|
+
const total = controlReport.passed + controlReport.failed;
|
|
40
|
+
if (controlReport.failed === 0) {
|
|
41
|
+
return `<p><small>${PASS} The checks are verified daily against the npm registry itself: all ${total} passed against ${what} on ${when(controlReport.finished)}.</small></p>`;
|
|
42
|
+
}
|
|
43
|
+
let html = `<div class="alert alert-warning"><p>${FAIL} The checks are verified daily against the npm registry itself, and on ${when(controlReport.finished)} ${controlReport.failed} of ${total} failed against ${what}. The npm registry is the reference, so those checks may be out of date - treat their results below with caution:</p><ul>`;
|
|
44
|
+
for (const step of controlReport.steps) {
|
|
45
|
+
for (const c of step.checks.filter(x => x.status === 'fail')) {
|
|
46
|
+
html += `<li>${escape(step.title)}: ${escape(c.title)} - ${escape(c.message)}</li>`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return html + '</ul></div>';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function intro() {
|
|
53
|
+
let html = '<p>Checks a registry against the npm registry protocol, the way an npm client uses it: search for a package by name, ';
|
|
54
|
+
html += 'read its package document, read one version\'s document, and download that version\'s tarball. ';
|
|
55
|
+
html += 'At each step it checks the parts of the response that npm clients depend on. ';
|
|
56
|
+
html += 'Give the registry\'s base URL (the one you would pass to <code>npm --registry</code>) and a package version it holds.</p>';
|
|
57
|
+
return html;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function renderForm({ values = {}, control, controlReport }) {
|
|
61
|
+
return intro() + form(values) + controlSummary(control, controlReport);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function renderReport({ report, control, controlReport }) {
|
|
65
|
+
const total = report.passed + report.failed;
|
|
66
|
+
let html = '';
|
|
67
|
+
html += `<p><strong>Registry:</strong> ${escape(report.registry)}<br>`;
|
|
68
|
+
html += `<strong>Package:</strong> ${escape(report.package)} ${escape(report.version)}<br>`;
|
|
69
|
+
html += `<strong>Run:</strong> ${when(report.started)}</p>`;
|
|
70
|
+
if (report.failed === 0) {
|
|
71
|
+
html += `<div class="alert alert-success">${PASS} All ${total} checks passed.</div>`;
|
|
72
|
+
} else {
|
|
73
|
+
html += `<div class="alert alert-danger">${FAIL} ${report.failed} of ${total} checks failed.</div>`;
|
|
74
|
+
}
|
|
75
|
+
html += controlSummary(control, controlReport);
|
|
76
|
+
|
|
77
|
+
for (const step of report.steps) {
|
|
78
|
+
html += `<h4 class="mt-4">${escape(step.title)}: <code>${escape(step.request)}</code></h4>`;
|
|
79
|
+
html += '<table class="grid">';
|
|
80
|
+
for (const c of step.checks) {
|
|
81
|
+
html += `<tr><td>${c.status === 'pass' ? PASS : FAIL}</td><td>${escape(c.title)}`;
|
|
82
|
+
if (c.status !== 'pass') {
|
|
83
|
+
html += `<br><span style="color: #dc3545">${escape(c.message)}</span>`;
|
|
84
|
+
const urls = [...new Set(c.uses.map(u => report.requests[u]?.url).filter(Boolean))];
|
|
85
|
+
if (urls.length > 0) {
|
|
86
|
+
html += `<br><small>${urls.map(escape).join('<br>')}</small>`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
html += '</td></tr>';
|
|
90
|
+
}
|
|
91
|
+
html += '</table>';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
html += '<h4 class="mt-4">Requests</h4>';
|
|
95
|
+
html += '<table class="grid"><tr><td><b>URL</b></td><td><b>Result</b></td></tr>';
|
|
96
|
+
for (const r of Object.values(report.requests)) {
|
|
97
|
+
const result = r.error ? `<span style="color: #dc3545">${escape(r.error)}</span>`
|
|
98
|
+
: `${escape(String(r.status))} ${escape(r.contentType || '')} (${escape(String(r.bytes))} bytes)`;
|
|
99
|
+
html += `<tr><td><small>${escape(r.url)}</small></td><td><small>${result}</small></td></tr>`;
|
|
100
|
+
}
|
|
101
|
+
html += '</table>';
|
|
102
|
+
|
|
103
|
+
html += '<h4 class="mt-4">Check again</h4>';
|
|
104
|
+
html += form({ registry: report.registry, package: report.package, version: report.version });
|
|
105
|
+
return html;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { renderForm, renderReport };
|