@jsenv/core 25.2.1 → 25.3.0-alpha.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/package.json +9 -5
- package/readme.md +54 -54
- package/src/internal/building/html/parseHtmlRessource.js +2 -1
- package/src/internal/jsenv_remote_directory.js +1 -1
- package/src/internal/integrity/integrity_algorithms.js +0 -26
- package/src/internal/integrity/integrity_parsing.js +0 -50
- package/src/internal/integrity/integrity_update.js +0 -23
- package/src/internal/integrity/integrity_validation.js +0 -49
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "25.
|
|
3
|
+
"version": "25.3.0-alpha.0",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,9 @@
|
|
|
27
27
|
"/src/",
|
|
28
28
|
"/main.js"
|
|
29
29
|
],
|
|
30
|
+
"workspaces": [
|
|
31
|
+
"./packages/*"
|
|
32
|
+
],
|
|
30
33
|
"scripts": {
|
|
31
34
|
"eslint": "node ./node_modules/eslint/bin/eslint.js . --ext=.js,.mjs,.cjs,.html",
|
|
32
35
|
"importmap": "node ./script/importmap/importmap.js",
|
|
@@ -39,8 +42,8 @@
|
|
|
39
42
|
"file-size": "node ./script/file_size/file_size.mjs --log",
|
|
40
43
|
"prettier": "prettier --write .",
|
|
41
44
|
"playwright-install": "npx playwright install-deps && npx playwright install",
|
|
42
|
-
"packages-install": "node ./script/packages/packages_install.js",
|
|
43
45
|
"certificate-install": "node ./script/dev/install_certificate_authority.mjs",
|
|
46
|
+
"sync-workspace-versions": "node ./script/dev/sync_workspace_versions.mjs",
|
|
44
47
|
"prepublishOnly": "npm run build"
|
|
45
48
|
},
|
|
46
49
|
"optionalDependencies": {
|
|
@@ -59,8 +62,9 @@
|
|
|
59
62
|
"@babel/plugin-transform-modules-systemjs": "7.16.5",
|
|
60
63
|
"@c88/v8-coverage": "0.1.1",
|
|
61
64
|
"@jsenv/abort": "4.1.2",
|
|
62
|
-
"@jsenv/filesystem": "2.
|
|
65
|
+
"@jsenv/filesystem": "2.7.0",
|
|
63
66
|
"@jsenv/importmap": "1.2.0",
|
|
67
|
+
"@jsenv/integrity": "0.0.1",
|
|
64
68
|
"@jsenv/log": "1.5.0",
|
|
65
69
|
"@jsenv/logger": "4.0.1",
|
|
66
70
|
"@jsenv/server": "12.3.2",
|
|
@@ -112,12 +116,12 @@
|
|
|
112
116
|
"@babel/plugin-transform-typescript": "7.16.1",
|
|
113
117
|
"@babel/preset-env": "7.16.5",
|
|
114
118
|
"@jsenv/assert": "2.4.1",
|
|
115
|
-
"@jsenv/babel-preset": "
|
|
119
|
+
"@jsenv/babel-preset": "1.1.2",
|
|
116
120
|
"@jsenv/eslint-config": "16.0.9",
|
|
117
121
|
"@jsenv/file-size-impact": "12.1.1",
|
|
118
122
|
"@jsenv/github-release-package": "1.2.3",
|
|
119
123
|
"@jsenv/https-local": "1.0.3",
|
|
120
|
-
"@jsenv/importmap-eslint-resolver": "5.2.
|
|
124
|
+
"@jsenv/importmap-eslint-resolver": "5.2.5",
|
|
121
125
|
"@jsenv/importmap-node-module": "5.1.0",
|
|
122
126
|
"@jsenv/package-publish": "1.6.2",
|
|
123
127
|
"@jsenv/performance-impact": "2.2.1",
|
package/readme.md
CHANGED
|
@@ -255,6 +255,60 @@ build duration: 1.85 seconds
|
|
|
255
255
|
|
|
256
256
|
To read more about jsenv build tool, check [jsenv build documentation](./docs/building/readme.md#jsenv-build).
|
|
257
257
|
|
|
258
|
+
# Installation
|
|
259
|
+
|
|
260
|
+
```console
|
|
261
|
+
npm install --save-dev @jsenv/core
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
_@jsenv/core_ is tested on Mac, Windows, Linux on Node.js 16.13.0. Other operating systems and Node.js versions are not tested.
|
|
265
|
+
|
|
266
|
+
# Configuration
|
|
267
|
+
|
|
268
|
+
We recommend to put some jsenv configuration in a top level file named _jsenv.config.mjs_.
|
|
269
|
+
|
|
270
|
+
The presence of a jsenv configuration file is **optional**.
|
|
271
|
+
|
|
272
|
+
```js
|
|
273
|
+
/*
|
|
274
|
+
* This file exports configuration reused by other files such as
|
|
275
|
+
*
|
|
276
|
+
* script/test/test.mjs
|
|
277
|
+
* script/build/build.mjs
|
|
278
|
+
*
|
|
279
|
+
* Read more at https://github.com/jsenv/jsenv-core#configuration
|
|
280
|
+
*/
|
|
281
|
+
|
|
282
|
+
export const projectDirectoryUrl = new URL("./", import.meta.url)
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
_jsenv.config.mjs_ is meant to share configuration, other files will simply import what they need.
|
|
286
|
+
|
|
287
|
+
```diff
|
|
288
|
+
import { buildProject } from '@jsenv/core'
|
|
289
|
+
|
|
290
|
+
+ import { projectDirectoryUrl } from "./jsenv.config.mjs"
|
|
291
|
+
|
|
292
|
+
await buildProject({
|
|
293
|
+
- projectDirectoryUrl: new URL('./', import.meta.url)
|
|
294
|
+
+ projectDirectoryUrl
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
> We recommend to use ".mjs" extension when a file is written for Node.js but you can name the file as you want, "jsenv.config.js" is fine too.
|
|
299
|
+
|
|
300
|
+
# Documentation
|
|
301
|
+
|
|
302
|
+
| Link | Description |
|
|
303
|
+
| -------------------------------------------------------- | ------------------------------------------ |
|
|
304
|
+
| [Browser support](./docs/browser_support/readme.md) | Documentation around browser support |
|
|
305
|
+
| [Assets](./docs/assets/readme.md) | How to use assets (CSS, JSON, images, ...) |
|
|
306
|
+
| [Web workers](./docs/web_workers/readme.md) | How to use web workers |
|
|
307
|
+
| [NPM package](./docs/npm_package/readme.md) | How to use a NPM package |
|
|
308
|
+
| [CDN](./docs/cdn/readme.md) | How to use ressources from CDN |
|
|
309
|
+
| [React](./docs/react/readme.md) | How to enable react (or preact) and JSX |
|
|
310
|
+
| [TypeScript (Experimental)](./docs/typescript/readme.md) | How to enable TypeScript |
|
|
311
|
+
|
|
258
312
|
# About
|
|
259
313
|
|
|
260
314
|
Jsenv was first created to write tests that could be executed in different runtimes. It has naturally evolved to cover the core needs of a JavaScript project:
|
|
@@ -324,60 +378,6 @@ The logo is composed by the name at the center and two circles orbiting around i
|
|
|
324
378
|
|
|
325
379
|
</details>
|
|
326
380
|
|
|
327
|
-
# Installation
|
|
328
|
-
|
|
329
|
-
```console
|
|
330
|
-
npm install --save-dev @jsenv/core
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
_@jsenv/core_ is tested on Mac, Windows, Linux on Node.js 16.13.0. Other operating systems and Node.js versions are not tested.
|
|
334
|
-
|
|
335
|
-
# Configuration
|
|
336
|
-
|
|
337
|
-
We recommend to put some jsenv configuration in a top level file named _jsenv.config.mjs_.
|
|
338
|
-
|
|
339
|
-
The presence of a jsenv configuration file is **optional**.
|
|
340
|
-
|
|
341
|
-
```js
|
|
342
|
-
/*
|
|
343
|
-
* This file exports configuration reused by other files such as
|
|
344
|
-
*
|
|
345
|
-
* script/test/test.mjs
|
|
346
|
-
* script/build/build.mjs
|
|
347
|
-
*
|
|
348
|
-
* Read more at https://github.com/jsenv/jsenv-core#configuration
|
|
349
|
-
*/
|
|
350
|
-
|
|
351
|
-
export const projectDirectoryUrl = new URL("./", import.meta.url)
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
_jsenv.config.mjs_ is meant to share configuration, other files will simply import what they need.
|
|
355
|
-
|
|
356
|
-
```diff
|
|
357
|
-
import { buildProject } from '@jsenv/core'
|
|
358
|
-
|
|
359
|
-
+ import { projectDirectoryUrl } from "./jsenv.config.mjs"
|
|
360
|
-
|
|
361
|
-
await buildProject({
|
|
362
|
-
- projectDirectoryUrl: new URL('./', import.meta.url)
|
|
363
|
-
+ projectDirectoryUrl
|
|
364
|
-
})
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
> We recommend to use ".mjs" extension when a file is written for Node.js but you can name the file as you want, "jsenv.config.js" is fine too.
|
|
368
|
-
|
|
369
|
-
# Documentation
|
|
370
|
-
|
|
371
|
-
| Link | Description |
|
|
372
|
-
| -------------------------------------------------------- | ------------------------------------------ |
|
|
373
|
-
| [Browser support](./docs/browser_support/readme.md) | Documentation around browser support |
|
|
374
|
-
| [Assets](./docs/assets/readme.md) | How to use assets (CSS, JSON, images, ...) |
|
|
375
|
-
| [CDN](./docs/cdn/readme.md) | How to use ressources from CDN |
|
|
376
|
-
| [Web workers](./docs/web_workers/readme.md) | How to use web workers |
|
|
377
|
-
| [NPM package](./docs/npm_package/readme.md) | How to use a NPM package |
|
|
378
|
-
| [React](./docs/react/readme.md) | How to enable react (or preact) and JSX |
|
|
379
|
-
| [TypeScript (Experimental)](./docs/typescript/readme.md) | How to enable TypeScript |
|
|
380
|
-
|
|
381
381
|
# See also
|
|
382
382
|
|
|
383
383
|
| Link | Description |
|
|
@@ -17,6 +17,8 @@ Or be sure to also reference this url somewhere in the html file like
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { urlToFilename, urlToRelativeUrl, resolveUrl } from "@jsenv/filesystem"
|
|
20
|
+
import { applyAlgoToRepresentationData } from "@jsenv/integrity"
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
import {
|
|
22
24
|
parseHtmlString,
|
|
@@ -48,7 +50,6 @@ import { collectNodesMutations } from "../parsing.utils.js"
|
|
|
48
50
|
|
|
49
51
|
import { collectSvgMutations } from "../svg/parseSvgRessource.js"
|
|
50
52
|
import { moveCssUrls } from "../css/moveCssUrls.js"
|
|
51
|
-
import { applyAlgoToRepresentationData } from "../../integrity/integrity_algorithms.js"
|
|
52
53
|
|
|
53
54
|
export const parseHtmlRessource = async (
|
|
54
55
|
htmlRessource,
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
urlToMeta,
|
|
7
7
|
writeFile,
|
|
8
8
|
} from "@jsenv/filesystem"
|
|
9
|
+
import { validateResponseIntegrity } from "@jsenv/integrity"
|
|
9
10
|
|
|
10
11
|
import { fetchUrl } from "@jsenv/core/src/internal/fetchUrl.js"
|
|
11
|
-
import { validateResponseIntegrity } from "@jsenv/core/src/internal/integrity/integrity_validation.js"
|
|
12
12
|
|
|
13
13
|
import { originDirectoryConverter } from "./origin_directory_converter.js"
|
|
14
14
|
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto"
|
|
2
|
-
|
|
3
|
-
export const isSupportedAlgorithm = (algo) => {
|
|
4
|
-
return SUPPORTED_ALGORITHMS.includes(algo)
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// https://www.w3.org/TR/SRI/#priority
|
|
8
|
-
export const getPrioritizedHashFunction = (firstAlgo, secondAlgo) => {
|
|
9
|
-
const firstIndex = SUPPORTED_ALGORITHMS.indexOf(firstAlgo)
|
|
10
|
-
const secondIndex = SUPPORTED_ALGORITHMS.indexOf(secondAlgo)
|
|
11
|
-
if (firstIndex === secondIndex) {
|
|
12
|
-
return ""
|
|
13
|
-
}
|
|
14
|
-
if (firstIndex < secondIndex) {
|
|
15
|
-
return secondAlgo
|
|
16
|
-
}
|
|
17
|
-
return firstAlgo
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const applyAlgoToRepresentationData = (algo, data) => {
|
|
21
|
-
const base64Value = crypto.createHash(algo).update(data).digest("base64")
|
|
22
|
-
return base64Value
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// keep this ordered by collision resistance as it is also used by "getPrioritizedHashFunction"
|
|
26
|
-
const SUPPORTED_ALGORITHMS = ["sha256", "sha384", "sha512"]
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { isSupportedAlgorithm } from "./integrity_algorithms.js"
|
|
2
|
-
|
|
3
|
-
// see https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
|
|
4
|
-
export const parseIntegrity = (string) => {
|
|
5
|
-
const integrityMetadata = {}
|
|
6
|
-
string
|
|
7
|
-
.trim()
|
|
8
|
-
.split(/\s+/)
|
|
9
|
-
.forEach((token) => {
|
|
10
|
-
const { isValid, algo, base64Value, optionExpression } =
|
|
11
|
-
parseAsHashWithOptions(token)
|
|
12
|
-
if (!isValid) {
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
if (!isSupportedAlgorithm(algo)) {
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
const metadataList = integrityMetadata[algo]
|
|
19
|
-
const metadata = { base64Value, optionExpression }
|
|
20
|
-
integrityMetadata[algo] = metadataList
|
|
21
|
-
? [...metadataList, metadata]
|
|
22
|
-
: [metadata]
|
|
23
|
-
})
|
|
24
|
-
return integrityMetadata
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// see https://w3c.github.io/webappsec-subresource-integrity/#the-integrity-attribute
|
|
28
|
-
const parseAsHashWithOptions = (token) => {
|
|
29
|
-
const dashIndex = token.indexOf("-")
|
|
30
|
-
if (dashIndex === -1) {
|
|
31
|
-
return { isValid: false }
|
|
32
|
-
}
|
|
33
|
-
const beforeDash = token.slice(0, dashIndex)
|
|
34
|
-
const afterDash = token.slice(dashIndex + 1)
|
|
35
|
-
const questionIndex = afterDash.indexOf("?")
|
|
36
|
-
const algo = beforeDash
|
|
37
|
-
if (questionIndex === -1) {
|
|
38
|
-
const base64Value = afterDash
|
|
39
|
-
const isValid = BASE64_REGEX.test(afterDash)
|
|
40
|
-
return { isValid, algo, base64Value }
|
|
41
|
-
}
|
|
42
|
-
const base64Value = afterDash.slice(0, questionIndex)
|
|
43
|
-
const optionExpression = afterDash.slice(questionIndex + 1)
|
|
44
|
-
const isValid =
|
|
45
|
-
BASE64_REGEX.test(afterDash) && VCHAR_REGEX.test(optionExpression)
|
|
46
|
-
return { isValid, algo, base64Value, optionExpression }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const BASE64_REGEX = /^[A-Za-z0-9+\/=+]+$/
|
|
50
|
-
const VCHAR_REGEX = /^[\x21-\x7E]+$/
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { parseIntegrity } from "./integrity_parsing.js"
|
|
2
|
-
import {
|
|
3
|
-
getPrioritizedHashFunction,
|
|
4
|
-
applyAlgoToRepresentationData,
|
|
5
|
-
} from "./integrity_algorithms.js"
|
|
6
|
-
|
|
7
|
-
export const updateIntegrity = (integrity, representationData) => {
|
|
8
|
-
const integrityMetadata = parseIntegrity(integrity)
|
|
9
|
-
const algos = Object.keys(integrityMetadata)
|
|
10
|
-
if (algos.length === 0) {
|
|
11
|
-
return ""
|
|
12
|
-
}
|
|
13
|
-
let strongestAlgo = algos[0]
|
|
14
|
-
algos.slice(1).forEach((algoCandidate) => {
|
|
15
|
-
strongestAlgo =
|
|
16
|
-
getPrioritizedHashFunction(strongestAlgo, algoCandidate) || strongestAlgo
|
|
17
|
-
})
|
|
18
|
-
const base64Value = applyAlgoToRepresentationData(
|
|
19
|
-
strongestAlgo,
|
|
20
|
-
representationData,
|
|
21
|
-
)
|
|
22
|
-
return `${strongestAlgo}-${base64Value}`
|
|
23
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { parseIntegrity } from "./integrity_parsing.js"
|
|
2
|
-
import {
|
|
3
|
-
getPrioritizedHashFunction,
|
|
4
|
-
applyAlgoToRepresentationData,
|
|
5
|
-
} from "./integrity_algorithms.js"
|
|
6
|
-
|
|
7
|
-
// https://www.w3.org/TR/SRI/#does-response-match-metadatalist
|
|
8
|
-
export const validateResponseIntegrity = (
|
|
9
|
-
{ url, type, dataRepresentation },
|
|
10
|
-
integrity,
|
|
11
|
-
) => {
|
|
12
|
-
if (!isResponseEligibleForIntegrityValidation({ type })) {
|
|
13
|
-
return false
|
|
14
|
-
}
|
|
15
|
-
const integrityMetadata = parseIntegrity(integrity)
|
|
16
|
-
const algos = Object.keys(integrityMetadata)
|
|
17
|
-
if (algos.length === 0) {
|
|
18
|
-
return true
|
|
19
|
-
}
|
|
20
|
-
let strongestAlgo = algos[0]
|
|
21
|
-
algos.slice(1).forEach((algoCandidate) => {
|
|
22
|
-
strongestAlgo =
|
|
23
|
-
getPrioritizedHashFunction(strongestAlgo, algoCandidate) || strongestAlgo
|
|
24
|
-
})
|
|
25
|
-
const metadataList = integrityMetadata[strongestAlgo]
|
|
26
|
-
const actualBase64Value = applyAlgoToRepresentationData(
|
|
27
|
-
strongestAlgo,
|
|
28
|
-
dataRepresentation,
|
|
29
|
-
)
|
|
30
|
-
const acceptedBase64Values = metadataList.map(
|
|
31
|
-
(metadata) => metadata.base64Value,
|
|
32
|
-
)
|
|
33
|
-
const someIsMatching = acceptedBase64Values.includes(actualBase64Value)
|
|
34
|
-
if (someIsMatching) {
|
|
35
|
-
return true
|
|
36
|
-
}
|
|
37
|
-
const error = new Error(
|
|
38
|
-
`Integrity validation failed for ressource "${url}". The integrity found for this ressource is "${strongestAlgo}-${actualBase64Value}"`,
|
|
39
|
-
)
|
|
40
|
-
error.code = "EINTEGRITY"
|
|
41
|
-
error.algorithm = strongestAlgo
|
|
42
|
-
error.found = actualBase64Value
|
|
43
|
-
throw error
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// https://www.w3.org/TR/SRI/#is-response-eligible-for-integrity-validation
|
|
47
|
-
const isResponseEligibleForIntegrityValidation = (response) => {
|
|
48
|
-
return ["basic", "cors", "default"].includes(response.type)
|
|
49
|
-
}
|