reffy 16.1.1 → 17.0.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/index.js +19 -10
- package/package.json +6 -5
- package/reffy.js +16 -15
- package/src/browserlib/extract-algorithms.mjs +14 -8
- package/src/cli/check-missing-dfns.js +23 -19
- package/src/cli/merge-crawl-results.js +12 -8
- package/src/cli/parse-webidl.js +9 -5
- package/src/lib/css-grammar-parser.js +1 -1
- package/src/lib/fetch.js +7 -12
- package/src/lib/mock-server.js +8 -5
- package/src/lib/post-processor.js +63 -23
- package/src/lib/specs-crawler.js +24 -17
- package/src/lib/throttled-queue.js +1 -1
- package/src/lib/util.js +40 -48
- package/src/postprocessing/annotate-links.js +2 -1
- package/src/postprocessing/csscomplete.js +2 -2
- package/src/postprocessing/events.js +2 -2
- package/src/postprocessing/idlnames.js +7 -7
- package/src/postprocessing/idlparsed.js +2 -2
- package/src/postprocessing/patch-dfns.js +1 -1
package/index.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { parseIdl } from "./src/cli/parse-webidl.js";
|
|
2
|
+
import { crawlSpecs } from "./src/lib/specs-crawler.js";
|
|
3
|
+
import { expandCrawlResult } from "./src/lib/util.js";
|
|
4
|
+
import { mergeCrawlResults } from "./src/lib/util.js";
|
|
5
|
+
import { isLatestLevelThatPasses } from "./src/lib/util.js";
|
|
6
|
+
import { getInterfaceTreeInfo } from "./src/lib/util.js";
|
|
7
|
+
import { getSchemaValidationFunction } from "./src/lib/util.js";
|
|
8
|
+
import postProcessor from "./src/lib/post-processor.js";
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
parseIdl,
|
|
12
|
+
crawlSpecs,
|
|
13
|
+
expandCrawlResult,
|
|
14
|
+
mergeCrawlResults,
|
|
15
|
+
isLatestLevelThatPasses,
|
|
16
|
+
getInterfaceTreeInfo,
|
|
17
|
+
getSchemaValidationFunction,
|
|
18
|
+
postProcessor
|
|
19
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reffy",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "17.0.0",
|
|
4
4
|
"description": "W3C/WHATWG spec dependencies exploration companion. Features a short set of tools to study spec references as well as WebIDL term definitions and references found in W3C specifications.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,23 +29,24 @@
|
|
|
29
29
|
"engines": {
|
|
30
30
|
"node": ">=20.12.1"
|
|
31
31
|
},
|
|
32
|
+
"type": "module",
|
|
32
33
|
"main": "index.js",
|
|
33
34
|
"bin": "./reffy.js",
|
|
34
35
|
"dependencies": {
|
|
35
|
-
"ajv": "8.
|
|
36
|
+
"ajv": "8.17.1",
|
|
36
37
|
"ajv-formats": "3.0.1",
|
|
37
38
|
"commander": "12.1.0",
|
|
38
39
|
"fetch-filecache-for-crawling": "5.1.1",
|
|
39
|
-
"puppeteer": "22.13.
|
|
40
|
+
"puppeteer": "22.13.1",
|
|
40
41
|
"semver": "^7.3.5",
|
|
41
42
|
"web-specs": "3.13.1",
|
|
42
43
|
"webidl2": "24.4.1"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
|
-
"mocha": "10.
|
|
46
|
+
"mocha": "10.7.0",
|
|
46
47
|
"respec": "35.1.1",
|
|
47
48
|
"respec-hljs": "2.1.1",
|
|
48
|
-
"rollup": "4.
|
|
49
|
+
"rollup": "4.19.0",
|
|
49
50
|
"undici": "^6.1.0"
|
|
50
51
|
},
|
|
51
52
|
"overrides": {
|
package/reffy.js
CHANGED
|
@@ -21,16 +21,17 @@
|
|
|
21
21
|
* @module crawler
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
import { Command } from 'commander';
|
|
25
|
+
import satisfies from 'semver/functions/satisfies.js';
|
|
26
|
+
import specs from 'web-specs' with { type: 'json' };
|
|
27
|
+
import packageConfig from './package.json' with { type: 'json' };
|
|
28
|
+
import { crawlSpecs } from './src/lib/specs-crawler.js';
|
|
29
|
+
import { modules } from './src/lib/post-processor.js';
|
|
30
|
+
import { loadJSON } from './src/lib/util.js';
|
|
31
31
|
|
|
32
32
|
// Warn if version of Node.js does not satisfy requirements
|
|
33
|
-
if (engines && engines.node &&
|
|
33
|
+
if (packageConfig.engines && packageConfig.engines.node &&
|
|
34
|
+
!satisfies(process.version, packageConfig.engines.node)) {
|
|
34
35
|
console.warn(`
|
|
35
36
|
[WARNING] Node.js ${process.version} detected but Reffy needs Node.js ${engines.node}.
|
|
36
37
|
Please consider upgrading Node.js if the program crashes!`);
|
|
@@ -54,14 +55,14 @@ function parseModuleOption(input) {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
function parseSpecOption(input) {
|
|
58
|
+
async function parseSpecOption(input) {
|
|
58
59
|
if (input === 'all') {
|
|
59
60
|
return specs
|
|
60
61
|
.filter(s => s.standing !== 'discontinued')
|
|
61
62
|
.map(s => s.shortname)
|
|
62
63
|
}
|
|
63
64
|
else {
|
|
64
|
-
const list =
|
|
65
|
+
const list = await loadJSON(input);
|
|
65
66
|
return list ?? input;
|
|
66
67
|
}
|
|
67
68
|
}
|
|
@@ -76,9 +77,9 @@ function parsePostOption(input) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
|
|
79
|
-
const program = new
|
|
80
|
+
const program = new Command();
|
|
80
81
|
program
|
|
81
|
-
.version(version)
|
|
82
|
+
.version(packageConfig.version)
|
|
82
83
|
.usage('[options]')
|
|
83
84
|
.description('Crawls and processes a list of Web specifications')
|
|
84
85
|
.option('-d, --debug', 'debug mode, crawl one spec at a time')
|
|
@@ -91,7 +92,7 @@ program
|
|
|
91
92
|
.option('-s, --spec <specs...>', 'specs to crawl')
|
|
92
93
|
.option('-t, --terse', 'output crawl results without metadata')
|
|
93
94
|
.option('-u, --use-crawl <folder>', 'use given crawl result folder as input for post-processing')
|
|
94
|
-
.action(options => {
|
|
95
|
+
.action(async options => {
|
|
95
96
|
if (!(options.output || options.module || options.spec || options.useCrawl)) {
|
|
96
97
|
console.error(`
|
|
97
98
|
At least one of the --output, --module, --spec or --use-crawl options needs to be
|
|
@@ -118,10 +119,10 @@ will dump ~100MB of data to the console:
|
|
|
118
119
|
crawlOptions.modules = options.module.map(parseModuleOption);
|
|
119
120
|
}
|
|
120
121
|
if (options.spec) {
|
|
121
|
-
crawlOptions.specs = options.spec.map(parseSpecOption).flat();
|
|
122
|
+
crawlOptions.specs = (await Promise.all(options.spec.map(parseSpecOption))).flat();
|
|
122
123
|
}
|
|
123
124
|
else {
|
|
124
|
-
crawlOptions.specs = parseSpecOption('all');
|
|
125
|
+
crawlOptions.specs = await parseSpecOption('all');
|
|
125
126
|
}
|
|
126
127
|
if (options.post) {
|
|
127
128
|
crawlOptions.post = options.post.map(parsePostOption).flat();
|
|
@@ -404,14 +404,17 @@ function getDefinedNameIn(el) {
|
|
|
404
404
|
*/
|
|
405
405
|
function findIntroParagraph(algo) {
|
|
406
406
|
let paragraph;
|
|
407
|
-
let container = algo.root.closest('
|
|
407
|
+
let container = algo.root.closest('li,.algorithm');
|
|
408
408
|
while (container) {
|
|
409
409
|
const dfn = container.querySelector('dfn');
|
|
410
|
-
if (dfn) {
|
|
411
|
-
paragraph = dfn.closest('p,div');
|
|
410
|
+
if (dfn && !algo.root.contains(dfn)) {
|
|
411
|
+
paragraph = dfn.closest('p,div,li');
|
|
412
|
+
break;
|
|
413
|
+
}
|
|
414
|
+
if (container.nodeName === 'LI') {
|
|
412
415
|
break;
|
|
413
416
|
}
|
|
414
|
-
container = container.parentElement.closest('
|
|
417
|
+
container = container.parentElement.closest('li,.algorithm');
|
|
415
418
|
}
|
|
416
419
|
|
|
417
420
|
if (!paragraph) {
|
|
@@ -441,9 +444,10 @@ function getAlgorithmInfo(algo, context) {
|
|
|
441
444
|
// Note some specs add the "algorithm" class to the `<ol>` and to the
|
|
442
445
|
// wrapping container, and define the name in the wrapping container.
|
|
443
446
|
let info = {};
|
|
447
|
+
|
|
444
448
|
let container = algo.root.closest('.algorithm');
|
|
445
|
-
|
|
446
|
-
|
|
449
|
+
if (!context?.nested) {
|
|
450
|
+
while (container) {
|
|
447
451
|
if (container.getAttribute('data-algorithm')) {
|
|
448
452
|
info.name = normalize(container.getAttribute('data-algorithm'));
|
|
449
453
|
if (container.getAttribute('data-algorithm-for')) {
|
|
@@ -462,13 +466,15 @@ function getAlgorithmInfo(algo, context) {
|
|
|
462
466
|
info.href = dfn.href;
|
|
463
467
|
}
|
|
464
468
|
}
|
|
465
|
-
break;
|
|
466
469
|
}
|
|
467
470
|
else {
|
|
468
471
|
info = getDefinedNameIn(container);
|
|
472
|
+
if (info.name || info.href) {
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
469
475
|
}
|
|
476
|
+
container = container.parentElement.closest('.algorithm');
|
|
470
477
|
}
|
|
471
|
-
container = container.parentElement.closest('.algorithm');
|
|
472
478
|
}
|
|
473
479
|
|
|
474
480
|
// Get the introductory prose from the previous paragraph
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
* @module checker
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import process from 'node:process';
|
|
28
|
+
import { loadJSON } from '../lib/util.js';
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* List of spec shortnames that, so far, don't follow the dfns data model
|
|
@@ -358,19 +361,19 @@ function matchIdlDfn(expected, actual,
|
|
|
358
361
|
* an array of missing CSS or IDL definitions. The function returns null when
|
|
359
362
|
* there are no missing definitions.
|
|
360
363
|
*/
|
|
361
|
-
function checkSpecDefinitions(spec, options = {}) {
|
|
364
|
+
async function checkSpecDefinitions(spec, options = {}) {
|
|
362
365
|
if (!options.includeObsolete && specsWithObsoleteDfnsModel.includes(spec.shortname)) {
|
|
363
366
|
return { obsoleteDfnsModel: true };
|
|
364
367
|
}
|
|
365
368
|
|
|
366
369
|
const dfns = (typeof spec.dfns === "string") ?
|
|
367
|
-
|
|
370
|
+
(await loadJSON(path.resolve(options.rootFolder, spec.dfns))).dfns :
|
|
368
371
|
(spec.dfns || []);
|
|
369
372
|
const css = (typeof spec.css === "string") ?
|
|
370
|
-
|
|
373
|
+
(await loadJSON(path.resolve(options.rootFolder, spec.css))) :
|
|
371
374
|
(spec.css || {});
|
|
372
375
|
const idl = (typeof spec.idlparsed === "string") ?
|
|
373
|
-
|
|
376
|
+
(await loadJSON(path.resolve(options.rootFolder, spec.idlparsed))).idlparsed :
|
|
374
377
|
spec.idlparsed;
|
|
375
378
|
|
|
376
379
|
// Make sure that all expected CSS definitions exist in the dfns extract
|
|
@@ -466,9 +469,9 @@ function checkSpecDefinitions(spec, options = {}) {
|
|
|
466
469
|
* identify the specification, and a `missing` property that is an object that
|
|
467
470
|
* may have `css` and `idl` properties which list missing CSS/IDL definitions.
|
|
468
471
|
*/
|
|
469
|
-
function checkDefinitions(pathToReport, options = {}) {
|
|
472
|
+
async function checkDefinitions(pathToReport, options = {}) {
|
|
470
473
|
const rootFolder = path.resolve(process.cwd(), pathToReport);
|
|
471
|
-
const index =
|
|
474
|
+
const index = (await loadJSON(path.resolve(rootFolder, 'index.json'))).results;
|
|
472
475
|
|
|
473
476
|
// Check all dfns against CSS and IDL extracts
|
|
474
477
|
const checkOptions = {
|
|
@@ -477,7 +480,7 @@ function checkDefinitions(pathToReport, options = {}) {
|
|
|
477
480
|
};
|
|
478
481
|
const missing = index
|
|
479
482
|
.filter(spec => !options.shortname || spec.shortname === options.shortname)
|
|
480
|
-
.map(spec => {
|
|
483
|
+
.map(async spec => {
|
|
481
484
|
const res = {
|
|
482
485
|
url: spec.url,
|
|
483
486
|
crawled: spec.crawled,
|
|
@@ -486,11 +489,10 @@ function checkDefinitions(pathToReport, options = {}) {
|
|
|
486
489
|
if (!spec.dfns) {
|
|
487
490
|
return res;
|
|
488
491
|
}
|
|
489
|
-
res.missing = checkSpecDefinitions(spec, checkOptions);
|
|
492
|
+
res.missing = await checkSpecDefinitions(spec, checkOptions);
|
|
490
493
|
return res;
|
|
491
494
|
});
|
|
492
|
-
|
|
493
|
-
return missing;
|
|
495
|
+
return Promise.all(missing);
|
|
494
496
|
}
|
|
495
497
|
|
|
496
498
|
|
|
@@ -516,26 +518,28 @@ function reportMissing(missing) {
|
|
|
516
518
|
/**************************************************
|
|
517
519
|
Export methods for use as module
|
|
518
520
|
**************************************************/
|
|
519
|
-
|
|
520
|
-
|
|
521
|
+
export {
|
|
522
|
+
checkSpecDefinitions,
|
|
523
|
+
checkDefinitions,
|
|
521
524
|
|
|
522
|
-
// "Inner" functions that the IDL names generator uses to link IDL terms with
|
|
523
|
-
// their definition (see generate-idlnames.js)
|
|
524
|
-
|
|
525
|
-
|
|
525
|
+
// "Inner" functions that the IDL names generator uses to link IDL terms with
|
|
526
|
+
// their definition (see generate-idlnames.js)
|
|
527
|
+
getExpectedDfnFromIdlDesc,
|
|
528
|
+
matchIdlDfn
|
|
529
|
+
};
|
|
526
530
|
|
|
527
531
|
|
|
528
532
|
|
|
529
533
|
/**************************************************
|
|
530
534
|
Code run if the code is run as a stand-alone module
|
|
531
535
|
**************************************************/
|
|
532
|
-
if (
|
|
536
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
533
537
|
const pathToReport = process.argv[2];
|
|
534
538
|
const shortname = process.argv[3] || 'all';
|
|
535
539
|
const format = process.argv[4] || 'markdown';
|
|
536
540
|
|
|
537
541
|
const options = (shortname === 'all') ? undefined : { shortname };
|
|
538
|
-
let res = checkDefinitions(pathToReport, options);
|
|
542
|
+
let res = await checkDefinitions(pathToReport, options);
|
|
539
543
|
if (shortname === 'all') {
|
|
540
544
|
res = res
|
|
541
545
|
.filter(result => result.missing &&
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
* @module merger
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import process from 'node:process';
|
|
21
|
+
import { loadJSON } from '../lib/util.js';
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -81,11 +83,11 @@ function mergeCrawlResults(newCrawl, refCrawl, options) {
|
|
|
81
83
|
* @param {Object} options Merge options. Only "matchTitle" is supported for now
|
|
82
84
|
* @return {Promise} The promise to have merged the two JSON files into one
|
|
83
85
|
*/
|
|
84
|
-
function mergeCrawlFiles(newCrawlPath, refCrawlPath, resPath, options) {
|
|
86
|
+
async function mergeCrawlFiles(newCrawlPath, refCrawlPath, resPath, options) {
|
|
85
87
|
options = options || {};
|
|
86
88
|
|
|
87
|
-
let newCrawl =
|
|
88
|
-
let refCrawl =
|
|
89
|
+
let newCrawl = await loadJSON(newCrawlPath);
|
|
90
|
+
let refCrawl = await loadJSON(refCrawlPath);
|
|
89
91
|
return mergeCrawlResults(newCrawl, refCrawl, options)
|
|
90
92
|
.then(filedata => new Promise((resolve, reject) =>
|
|
91
93
|
fs.writeFile(resPath, JSON.stringify(filedata, null, 2),
|
|
@@ -96,14 +98,16 @@ function mergeCrawlFiles(newCrawlPath, refCrawlPath, resPath, options) {
|
|
|
96
98
|
/**************************************************
|
|
97
99
|
Export the methods for use as module
|
|
98
100
|
**************************************************/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
export {
|
|
102
|
+
mergeCrawlResults,
|
|
103
|
+
mergeCrawlFiles
|
|
104
|
+
};
|
|
101
105
|
|
|
102
106
|
|
|
103
107
|
/**************************************************
|
|
104
108
|
Code run if the code is run as a stand-alone module
|
|
105
109
|
**************************************************/
|
|
106
|
-
if (
|
|
110
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
107
111
|
let newCrawlPath = process.argv[2];
|
|
108
112
|
let refCrawlPath = process.argv[3];
|
|
109
113
|
let resPath = process.argv[4];
|
package/src/cli/parse-webidl.js
CHANGED
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
* @module webidlParser
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
import * as WebIDL2 from 'webidl2';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import process from 'node:process';
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -410,15 +412,17 @@ function addDependency(name, idlNames, externalDependencies) {
|
|
|
410
412
|
/**************************************************
|
|
411
413
|
Export the parse method for use as module
|
|
412
414
|
**************************************************/
|
|
413
|
-
|
|
414
|
-
|
|
415
|
+
export {
|
|
416
|
+
parse,
|
|
417
|
+
hasObsoleteIdl
|
|
418
|
+
};
|
|
415
419
|
|
|
416
420
|
|
|
417
421
|
/**************************************************
|
|
418
422
|
Code run if the code is run as a stand-alone module
|
|
419
423
|
**************************************************/
|
|
420
|
-
if (
|
|
421
|
-
const fs =
|
|
424
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
425
|
+
const fs = await import('node:fs');
|
|
422
426
|
const idlFile = process.argv[2];
|
|
423
427
|
if (!idlFile) {
|
|
424
428
|
console.error("No IDL file to parse");
|
package/src/lib/fetch.js
CHANGED
|
@@ -5,16 +5,14 @@
|
|
|
5
5
|
* @module finder
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import baseFetch from 'fetch-filecache-for-crawling';
|
|
11
|
+
import { loadJSON } from './util.js';
|
|
11
12
|
|
|
12
13
|
// Read configuration parameters from `config.json` file
|
|
13
|
-
let config =
|
|
14
|
-
|
|
15
|
-
config = require(path.resolve('config.json'));
|
|
16
|
-
}
|
|
17
|
-
catch (err) {
|
|
14
|
+
let config = await loadJSON('config.json');
|
|
15
|
+
if (!config) {
|
|
18
16
|
config = {};
|
|
19
17
|
}
|
|
20
18
|
|
|
@@ -32,7 +30,7 @@ catch (err) {
|
|
|
32
30
|
* options for fetch-filecache-for-crawling)
|
|
33
31
|
* @return {Promise(Response)} Promise to get an HTTP response
|
|
34
32
|
*/
|
|
35
|
-
async function fetch(url, options) {
|
|
33
|
+
export default async function fetch(url, options) {
|
|
36
34
|
options = Object.assign({headers: {}}, options);
|
|
37
35
|
['cacheFolder', 'resetCache', 'cacheRefresh', 'logToConsole'].forEach(param => {
|
|
38
36
|
let fetchParam = (param === 'cacheRefresh') ? 'refresh' : param;
|
|
@@ -51,6 +49,3 @@ async function fetch(url, options) {
|
|
|
51
49
|
|
|
52
50
|
return baseFetch(url, options);
|
|
53
51
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
module.exports = fetch;
|
package/src/lib/mock-server.js
CHANGED
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* @module mock-server
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { MockAgent, setGlobalDispatcher } from 'undici';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
const scriptPath = path.dirname(fileURLToPath(import.meta.url));
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Determine the path to the "node_modules" folder. The path depends on whether
|
|
@@ -17,7 +20,7 @@ const { existsSync, readFileSync } = require('fs');
|
|
|
17
20
|
* @return {String} Path to the node_modules folder.
|
|
18
21
|
*/
|
|
19
22
|
function getModulesFolder() {
|
|
20
|
-
const rootFolder = path.resolve(
|
|
23
|
+
const rootFolder = path.resolve(scriptPath, '../..');
|
|
21
24
|
let folder = path.resolve(rootFolder, 'node_modules');
|
|
22
25
|
if (existsSync(folder)) {
|
|
23
26
|
return folder;
|
|
@@ -185,4 +188,4 @@ nock.emitter.on('no match', function(req, options, requestBody) {
|
|
|
185
188
|
}
|
|
186
189
|
});*/
|
|
187
190
|
|
|
188
|
-
|
|
191
|
+
export default mockAgent;
|
|
@@ -47,24 +47,69 @@
|
|
|
47
47
|
* @module
|
|
48
48
|
*/
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
import fs from 'node:fs';
|
|
51
|
+
import path from 'node:path';
|
|
52
|
+
import { pathToFileURL } from 'node:url';
|
|
53
|
+
import { createFolderIfNeeded } from './util.js';
|
|
54
|
+
import csscomplete from '../postprocessing/csscomplete.js';
|
|
55
|
+
import events from '../postprocessing/events.js';
|
|
56
|
+
import idlnames from '../postprocessing/idlnames.js';
|
|
57
|
+
import idlparsed from '../postprocessing/idlparsed.js';
|
|
58
|
+
import annotatelinks from '../postprocessing/annotate-links.js';
|
|
59
|
+
import patchdfns from '../postprocessing/patch-dfns.js';
|
|
53
60
|
|
|
54
61
|
|
|
55
62
|
/**
|
|
56
63
|
* Core post-processing modules
|
|
57
64
|
*/
|
|
58
65
|
const modules = {
|
|
59
|
-
csscomplete
|
|
60
|
-
events
|
|
61
|
-
idlnames
|
|
62
|
-
idlparsed
|
|
63
|
-
annotatelinks
|
|
64
|
-
patchdfns
|
|
66
|
+
csscomplete,
|
|
67
|
+
events,
|
|
68
|
+
idlnames,
|
|
69
|
+
idlparsed,
|
|
70
|
+
annotatelinks,
|
|
71
|
+
patchdfns
|
|
65
72
|
};
|
|
66
73
|
|
|
67
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Custom post-processing modules
|
|
77
|
+
*/
|
|
78
|
+
const customModules = {};
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Loads post-processing modules once and for all
|
|
83
|
+
*/
|
|
84
|
+
async function loadModules(mods) {
|
|
85
|
+
for (const mod of mods) {
|
|
86
|
+
if (typeof mod === 'string') {
|
|
87
|
+
if (modules[mod]) {
|
|
88
|
+
// Core post-processing module, already loaded
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
try {
|
|
93
|
+
customModules[mod] = await import(pathToFileURL(mod));
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
throw new Error(`Unknown post-processing module "${mod}"`);
|
|
97
|
+
}
|
|
98
|
+
if (!isModuleValid(customModules[mod])) {
|
|
99
|
+
throw new Error(`"${mod}" is not a valid post-processing module`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// Given post-processing moduel should already be a good one
|
|
105
|
+
if (!isModuleValid(customModules[mod])) {
|
|
106
|
+
throw new Error(`Post-processing module given as parameter does not have a "run" function`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
68
113
|
/**
|
|
69
114
|
* Returns the post-processing module that match the requested name, or the
|
|
70
115
|
* given parameter if it is a post-processing module already
|
|
@@ -79,20 +124,13 @@ function getModule(mod) {
|
|
|
79
124
|
if (modules[mod]) {
|
|
80
125
|
return Object.assign({ name: mod }, modules[mod]);
|
|
81
126
|
}
|
|
127
|
+
else if (customModules[mod]) {
|
|
128
|
+
return Object.assign({ name: mod }, customModules[mod]);
|
|
129
|
+
}
|
|
82
130
|
else {
|
|
83
|
-
|
|
84
|
-
if (!fmod) {
|
|
85
|
-
throw new Error(`Unknown post-processing module "${mod}"`);
|
|
86
|
-
}
|
|
87
|
-
if (!isModuleValid(fmod)) {
|
|
88
|
-
throw new Error(`"${mod}" is not a valid post-processing module`);
|
|
89
|
-
}
|
|
90
|
-
return Object.assign({ name: mod }, fmod);
|
|
131
|
+
throw new Error(`Unknown post-processing module "${mod}"`);
|
|
91
132
|
}
|
|
92
133
|
}
|
|
93
|
-
else if (!isModuleValid(mod)) {
|
|
94
|
-
throw new Error(`Post-processing module given as parameter does not have a "run" function`);
|
|
95
|
-
}
|
|
96
134
|
return mod;
|
|
97
135
|
}
|
|
98
136
|
|
|
@@ -270,11 +308,13 @@ function appliesAtLevel(mod, level) {
|
|
|
270
308
|
/**************************************************
|
|
271
309
|
Export post-processing functions
|
|
272
310
|
**************************************************/
|
|
273
|
-
|
|
274
|
-
|
|
311
|
+
const moduleNames = Object.keys(modules);
|
|
312
|
+
export {
|
|
313
|
+
moduleNames as modules,
|
|
314
|
+
loadModules,
|
|
275
315
|
run, save,
|
|
276
316
|
extractsPerSeries,
|
|
277
317
|
dependsOn,
|
|
278
318
|
getProperty,
|
|
279
319
|
appliesAtLevel
|
|
280
|
-
};
|
|
320
|
+
};
|
package/src/lib/specs-crawler.js
CHANGED
|
@@ -10,27 +10,27 @@
|
|
|
10
10
|
* @module crawler
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const {
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { inspect } from 'node:util';
|
|
16
|
+
import specs from 'web-specs' with { type: 'json' };
|
|
17
|
+
import * as postProcessor from './post-processor.js';
|
|
18
|
+
import ThrottledQueue from './throttled-queue.js';
|
|
19
|
+
import {
|
|
21
20
|
completeWithAlternativeUrls,
|
|
22
21
|
expandBrowserModules,
|
|
23
22
|
expandCrawlResult,
|
|
24
23
|
expandSpecResult,
|
|
25
24
|
isLatestLevelThatPasses,
|
|
26
25
|
processSpecification,
|
|
27
|
-
requireFromWorkingDirectory,
|
|
28
26
|
setupBrowser,
|
|
29
27
|
teardownBrowser,
|
|
30
|
-
createFolderIfNeeded
|
|
31
|
-
|
|
28
|
+
createFolderIfNeeded,
|
|
29
|
+
loadJSON
|
|
30
|
+
} from './util.js';
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
import packageConfig from '../../package.json' with { type: 'json' };
|
|
33
|
+
const reffyVersion = packageConfig.version;
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -342,6 +342,9 @@ async function crawlList(speclist, crawlOptions) {
|
|
|
342
342
|
list = list.filter(spec => !!spec.release);
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
+
// Load post-processing modules as needed
|
|
346
|
+
await postProcessor.loadModules(crawlOptions.post ?? []);
|
|
347
|
+
|
|
345
348
|
const nbStr = '' + list.length;
|
|
346
349
|
async function processSpec(spec, idx) {
|
|
347
350
|
const logCounter = ('' + (idx + 1)).padStart(nbStr.length, ' ') + '/' + nbStr;
|
|
@@ -465,7 +468,7 @@ async function saveResults(contents, settings) {
|
|
|
465
468
|
* See CLI help (node reffy.js --help) for details.
|
|
466
469
|
* @return {Promise<void>} The promise that the crawl will have been made
|
|
467
470
|
*/
|
|
468
|
-
function crawlSpecs(options) {
|
|
471
|
+
async function crawlSpecs(options) {
|
|
469
472
|
function prepareListOfSpecs(list) {
|
|
470
473
|
return list.map(spec => {
|
|
471
474
|
if (typeof spec !== 'string') {
|
|
@@ -506,7 +509,7 @@ function crawlSpecs(options) {
|
|
|
506
509
|
}
|
|
507
510
|
|
|
508
511
|
const crawlIndex = options?.useCrawl ?
|
|
509
|
-
|
|
512
|
+
await loadJSON(path.join(options.useCrawl, 'index.json')) :
|
|
510
513
|
null;
|
|
511
514
|
|
|
512
515
|
const requestedList = crawlIndex ? crawlIndex.results :
|
|
@@ -606,6 +609,10 @@ Export methods for use as module
|
|
|
606
609
|
// - "crawlSpecs" takes options as input, runs all steps and saves results
|
|
607
610
|
// to files (or outputs the results to the console). It does not return
|
|
608
611
|
// anything.
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
+
function crawl(...args) {
|
|
613
|
+
return Array.isArray(args[0]) ?
|
|
614
|
+
crawlList.apply(this, args) :
|
|
615
|
+
crawlSpecs.apply(this, args);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export { crawl as crawlSpecs };
|
|
@@ -43,7 +43,7 @@ function getOrigin(url) {
|
|
|
43
43
|
* while guaranteeing that only one request will be sent to a given origin
|
|
44
44
|
* server at a time.
|
|
45
45
|
*/
|
|
46
|
-
|
|
46
|
+
export default class ThrottledQueue {
|
|
47
47
|
originQueue = {};
|
|
48
48
|
maxParallel = 4;
|
|
49
49
|
sleepInterval = 2000;
|
package/src/lib/util.js
CHANGED
|
@@ -2,26 +2,27 @@
|
|
|
2
2
|
* A bunch of utility functions common to multiple scripts
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import crypto from 'node:crypto';
|
|
8
|
+
import { Buffer } from 'node:buffer';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import puppeteer from 'puppeteer';
|
|
11
|
+
import Ajv from 'ajv';
|
|
12
|
+
import addFormats from 'ajv-formats';
|
|
13
|
+
import fetch from './fetch.js';
|
|
14
|
+
|
|
15
|
+
import commonSchema from '../../schemas/common.json' with { type: 'json' };
|
|
16
|
+
import specEquivalents from '../specs/spec-equivalents.json' with { type: 'json' };
|
|
17
|
+
import reffyModules from '../browserlib/reffy.json' with { type: 'json' };
|
|
18
|
+
|
|
19
|
+
const scriptPath = path.dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Maximum depth difference supported between Reffy's install path and custom
|
|
22
23
|
* modules that may be provided on the command-line
|
|
23
24
|
*
|
|
24
|
-
* TODO: Find a way to get
|
|
25
|
+
* TODO: Find a way to get rid of that, there should be no limit
|
|
25
26
|
*/
|
|
26
27
|
const maxPathDepth = 20;
|
|
27
28
|
|
|
@@ -38,39 +39,30 @@ const prop = p => x => x[p];
|
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
|
-
*
|
|
42
|
-
* current working directory (CWD), instead of relative to the current JS
|
|
43
|
-
* file.
|
|
44
|
-
*
|
|
45
|
-
* This is typically needed to be able to use "require" to load JSON config
|
|
46
|
-
* files provided as command-line arguments.
|
|
42
|
+
* Load a JSON file as JS object.
|
|
47
43
|
*
|
|
48
44
|
* @function
|
|
49
45
|
* @param {String} filename The path to the file to require
|
|
50
|
-
* @return {Object} The result of
|
|
51
|
-
* working directory.
|
|
46
|
+
* @return {Object} The result of loading and parsing the file relative to the
|
|
47
|
+
* current working directory.
|
|
52
48
|
*/
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
49
|
+
async function loadJSON(filename) {
|
|
50
|
+
try {
|
|
51
|
+
const json = await fs.readFile(filename, 'utf8');
|
|
52
|
+
return JSON.parse(json);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
61
58
|
|
|
62
59
|
|
|
63
60
|
/**
|
|
64
61
|
* Path to the "webidl2" folder to resolve relative links in the ES6 browser
|
|
65
62
|
* lib modules. The path depends on whether Reffy is run directly, or installed
|
|
66
63
|
* as a library.
|
|
67
|
-
*
|
|
68
|
-
* Code relies on the "require.resolve" function, but note that, when given a
|
|
69
|
-
* simple module name, that function returns the path to the file targeted by
|
|
70
|
-
* the "main" property in "package.json" which, in the case of the webidl2
|
|
71
|
-
* module, is "dist/webidl2.js".
|
|
72
64
|
*/
|
|
73
|
-
const webidl2Folder = path.
|
|
65
|
+
const webidl2Folder = path.dirname(fileURLToPath(import.meta.resolve('webidl2')));
|
|
74
66
|
|
|
75
67
|
|
|
76
68
|
/**
|
|
@@ -128,7 +120,7 @@ function expandBrowserModules(modules) {
|
|
|
128
120
|
return name;
|
|
129
121
|
}
|
|
130
122
|
|
|
131
|
-
const browserlibPath = path.resolve(
|
|
123
|
+
const browserlibPath = path.resolve(scriptPath, '..', 'browserlib');
|
|
132
124
|
if (!modules) {
|
|
133
125
|
return reffyModules.map(mod => Object.assign({
|
|
134
126
|
name: getCamelCaseName(mod.href),
|
|
@@ -391,7 +383,7 @@ async function processSpecification(spec, processFunction, args, options) {
|
|
|
391
383
|
const requestPath = request.url.substring(request.url.indexOf(reffyPath) + reffyPath.length);
|
|
392
384
|
let depth = requestPath.lastIndexOf('__/') / 3;
|
|
393
385
|
const filename = requestPath.substring(requestPath.lastIndexOf('__/') + 3);
|
|
394
|
-
let filePath = path.resolve(
|
|
386
|
+
let filePath = path.resolve(scriptPath, '..', 'browserlib');
|
|
395
387
|
while (depth < maxPathDepth - 1) {
|
|
396
388
|
filePath = path.resolve(filePath, '..');
|
|
397
389
|
depth += 1;
|
|
@@ -1041,7 +1033,7 @@ function getInterfaceTreeInfo(iface, interfaces) {
|
|
|
1041
1033
|
* @return {function} The "validate" function for Ajv. The function returns null
|
|
1042
1034
|
* if the requested schema does not exist.
|
|
1043
1035
|
*/
|
|
1044
|
-
function getSchemaValidationFunction(schemaName) {
|
|
1036
|
+
async function getSchemaValidationFunction(schemaName) {
|
|
1045
1037
|
// Helper function that selects the right schema file from the given
|
|
1046
1038
|
// schema name.
|
|
1047
1039
|
function getSchemaFileFromSchemaName(name) {
|
|
@@ -1065,11 +1057,11 @@ function getSchemaValidationFunction(schemaName) {
|
|
|
1065
1057
|
}
|
|
1066
1058
|
}
|
|
1067
1059
|
|
|
1068
|
-
const schemasFolder = path.
|
|
1060
|
+
const schemasFolder = path.resolve(scriptPath, '..', '..', 'schemas');
|
|
1069
1061
|
const schemaFile = getSchemaFileFromSchemaName(schemaName);
|
|
1070
1062
|
let schema;
|
|
1071
1063
|
try {
|
|
1072
|
-
schema =
|
|
1064
|
+
schema = await loadJSON(path.join(schemasFolder, schemaFile));
|
|
1073
1065
|
}
|
|
1074
1066
|
catch (err) {
|
|
1075
1067
|
return null;
|
|
@@ -1082,10 +1074,11 @@ function getSchemaValidationFunction(schemaName) {
|
|
|
1082
1074
|
// The files schemas reference the browserlib ones, which need to
|
|
1083
1075
|
// be explicitly added for Ajv to resolve references
|
|
1084
1076
|
const folder = path.join(schemasFolder, 'browserlib');
|
|
1085
|
-
const files =
|
|
1077
|
+
const files = await fs.readdir(folder);
|
|
1086
1078
|
for (const file of files) {
|
|
1087
1079
|
if (file.endsWith('.json')) {
|
|
1088
|
-
|
|
1080
|
+
const json = await loadJSON(path.join(schemasFolder, 'browserlib', file));
|
|
1081
|
+
ajvWithSchemas = ajvWithSchemas.addSchema(json);
|
|
1089
1082
|
}
|
|
1090
1083
|
}
|
|
1091
1084
|
}
|
|
@@ -1118,10 +1111,8 @@ function getSchemaValidationFunction(schemaName) {
|
|
|
1118
1111
|
};
|
|
1119
1112
|
}
|
|
1120
1113
|
|
|
1121
|
-
|
|
1122
|
-
module.exports = {
|
|
1114
|
+
export {
|
|
1123
1115
|
fetch,
|
|
1124
|
-
requireFromWorkingDirectory,
|
|
1125
1116
|
expandBrowserModules,
|
|
1126
1117
|
setupBrowser,
|
|
1127
1118
|
teardownBrowser,
|
|
@@ -1133,5 +1124,6 @@ module.exports = {
|
|
|
1133
1124
|
getGeneratedIDLNamesByCSSProperty,
|
|
1134
1125
|
createFolderIfNeeded,
|
|
1135
1126
|
getInterfaceTreeInfo,
|
|
1136
|
-
getSchemaValidationFunction
|
|
1127
|
+
getSchemaValidationFunction,
|
|
1128
|
+
loadJSON
|
|
1137
1129
|
};
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* rather completes the `css` property with additional info.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import { getGeneratedIDLNamesByCSSProperty } from '../lib/util.js';
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
export default {
|
|
14
14
|
dependsOn: ['css', 'dfns'],
|
|
15
15
|
input: 'spec',
|
|
16
16
|
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* per event.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { isLatestLevelThatPasses, getInterfaceTreeInfo } from '../lib/util.js';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
export default {
|
|
9
9
|
dependsOn: ['events'],
|
|
10
10
|
input: 'crawl',
|
|
11
11
|
property: 'events',
|
|
@@ -7,20 +7,20 @@
|
|
|
7
7
|
* to worry about ordering, "idlparsed" will always run before this one).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import {
|
|
13
13
|
matchIdlDfn,
|
|
14
|
-
getExpectedDfnFromIdlDesc }
|
|
15
|
-
|
|
14
|
+
getExpectedDfnFromIdlDesc } from '../cli/check-missing-dfns.js';
|
|
15
|
+
import {
|
|
16
16
|
isLatestLevelThatPasses,
|
|
17
|
-
createFolderIfNeeded }
|
|
17
|
+
createFolderIfNeeded } from '../lib/util.js';
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Definition of the post-processing module
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
export default {
|
|
24
24
|
dependsOn: ['idlparsed', 'dfns'],
|
|
25
25
|
input: 'crawl',
|
|
26
26
|
run: generateIdlNames,
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* The module runs at the spec level and generates an `idlparsed` property.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
import * as webidlParser from '../cli/parse-webidl.js';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
export default {
|
|
11
11
|
dependsOn: ['dfns', 'idl'],
|
|
12
12
|
input: 'spec',
|
|
13
13
|
property: 'idlparsed',
|