From 4291ed8bd3b52a9a32f61872aedf0e4f2016dedc Mon Sep 17 00:00:00 2001 From: Francois Daoust Date: Tue, 19 Mar 2024 17:00:20 +0100 Subject: [PATCH 1/2] Update build-diff script to also accept a URL This update makes the `src/build-diff.js` script accept the canonical URL of a spec as parameter, and build the updates to `index.json` that this URL would trigger if it were added to `specs.json` (or if it would replace the corresponding entry in `specs.json`). The script still accepts commit references, although the syntax was slightly updated to get back to one parameter and better align with `git diff`, from `node src/build-diff HEAD HEAD~2` to `node src/build-diff HEAD..HEAD~2`. The script is now also available as a module and the exported function also accepts additional spec properties on top of the URL. That is not yet used but will be useful for later. Next step is to create a more proper CLI that makes use of the module, can also run tests on the updates, and propose a human-readable report that could be added to a pull request and/or used as commit message. --- src/build-diff.js | 165 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 147 insertions(+), 18 deletions(-) diff --git a/src/build-diff.js b/src/build-diff.js index 6618d8e7..7681c01f 100644 --- a/src/build-diff.js +++ b/src/build-diff.js @@ -1,3 +1,24 @@ +/** + * Script that builds the diff that the provided change(s) to `specs.json` + * would entail to `index.json`. + * + * The script takes the canonical URL of a spec as input, or a "git diff"-like + * reference to named commit(s), in which case it compiles the list of changes + * from the differences in `specs.json` between both commits. It computes the + * updates that the update(s) would trigger to `index.json` and reports the + * diff in a JSON structure with `add`, `update`, `delete`, and `seriesUpdate` + * properties. + * + * For named commits, "working" is the equivalent of Git's "--cached" option + * and means "use the working copy of specs.json". + * + * Examples: + * node src/build-diff https://www.w3.org/TR/webrtc/ + * node src/build-diff working + * node src/build-diff HEAD + * node src/build-diff HEAD..HEAD~3 + */ + const assert = require("assert"); const path = require("path"); const { execSync } = require("child_process"); @@ -72,12 +93,68 @@ function areIdentical(s1, s2) { } } + +/** + * Build the diff for the given spec or list of changes. The first parameter + * may be the canonical URL of a spec, a named Git commit, or two named Git + * commits separated by two dots. Named Git commit are used to compile the + * changes in specs.json that need to be built. + * + * For instance: + * - https://w3c.github.io/example-spec/ + * - HEAD + * - HEAD..HEAD~3 + * + * Internally, the function branches to `buildCommits` or `buildSpec` + * depending on what needs to be built. + * + * If what to build is the canonical URL of a spec and the options contain a + * `custom` property, that property is used to complete the initial info for + * the spec. + * + * The function throws in case of errors. + */ +async function build(what, options) { + if (!what) { + throw new Error('Nothing to build'); + } + const reCommit = /^([\w~\^]+)(?:\.\.([\w~\^]+))?$/; + const commitMatch = what.match(reCommit); + if (commitMatch) { + // We seem to have received a named + let from = commitMatch[2]; + const to = commitMatch[1]; + if (!from) { + from = (to.toLowerCase() === 'working') ? 'HEAD' : 'HEAD~1'; + } + return buildCommits(to, from, options); + } + else { + // We seem to have received a URL + let url; + try { + url = new URL(what); + } + catch (err) { + throw new Error('Invalid what argument received. Should be a URL, a named commit, or a couple of named commit separated by two dots (..)'); + } + const custom = options?.custom ?? {}; + const spec = Object.assign({}, custom, { url: url.toString() }); + return buildSpec(spec, options); + } +} + + /** - * Generate the new index file from the given initial list file. + * Build the diff for changes made to `specs.json` between the provided named + * Git commits. + * + * Internally, the function compiles the diff and then hands it over to + * `buildDiff`. * * The function throws in case of errors. */ -async function compareIndex(newRef, baseRef, { diffType = "diff", log = console.log }) { +async function buildCommits(newRef, baseRef, { diffType = "diff", log = console.log }) { log(`Retrieve specs.json at "${newRef}"...`); let newSpecs; if (newRef.toLowerCase() === "working") { @@ -119,6 +196,45 @@ async function compareIndex(newRef, baseRef, { diffType = "diff", log = console. } log(`Compute specs.json diff... done`); + return buildDiff(diff, baseSpecs, baseIndex, { diffType, log }); +} + + +/** + * Build the diff for the given spec. + * + * The spec object must have a `url` property. It may have other properties. + * + * Internally, the function turns the parameter into a diff and hands it over + * to `buildSpec`. + * + * The function throws in case of errors. + */ +async function buildSpec(spec, { diffType = "diff", log = console.log }) { + log(`Retrieve specs.json...`); + const baseSpecs = require(path.resolve(__dirname, "..", "specs.json")); + log(`Retrieve specs.json... done`); + + log(`Retrieve index.json...`); + const baseIndex = require(path.resolve(__dirname, "..", "index.json")); + log(`Retrieve index.json... done`); + + log(`Prepare diff...`); + const isNew = !baseSpecs.find(s => haveSameUrl(s, spec)); + log(isNew ? `- spec is new` : `- spec is already in specs.json`); + const diff = { + add: isNew ? [spec] : [], + update: isNew ? [] : [spec], + delete: [] + }; + log(`Prepare diff... done`); + + return buildDiff(diff, baseSpecs, baseIndex, { diffType, log }); +} + + +async function buildDiff(diff, baseSpecs, baseIndex, { diffType = "diff", log = console.log }) { + diff = Object.assign({}, diff); log(`Delete specs that were dropped...`); diff.delete = baseIndex.filter(spec => diff.delete.find(s => haveSameUrl(s, spec))); let newIndex = baseIndex.filter(spec => !diff.delete.find(s => haveSameUrl(s, spec))); @@ -136,7 +252,9 @@ async function compareIndex(newRef, baseRef, { diffType = "diff", log = console. previousIndex: newIndex, log: function(...msg) { log(' ', ...msg); } }); diff.add = diff.add.map(spec => built.find(s => haveSameUrl(s, spec))); - diff.update = diff.update.map(spec => built.find(s => haveSameUrl(s, spec))); + diff.update = diff.update + .map(spec => built.find(s => haveSameUrl(s, spec))) + .filter(spec => !areIdentical(spec, baseIndex.find(s => s.url === spec.url))); diff.seriesUpdate = built .filter(spec => !diff.add.find(s => haveSameUrl(s, spec)) && @@ -159,21 +277,32 @@ async function compareIndex(newRef, baseRef, { diffType = "diff", log = console. } +/******************************************************************************* +Export main function for use as module +*******************************************************************************/ +module.exports = { + build, + buildCommits, + buildSpec +}; + + /******************************************************************************* Main loop *******************************************************************************/ -const newRef = process.argv[2] ?? "working"; -const baseRef = process.argv[3] ?? "HEAD"; -const diffType = process.argv[4] ?? "diff"; - -compareIndex(newRef, baseRef, { diffType, log: console.warn }) - .then(diff => { - // Note: using process.stdout.write to avoid creating a final newline in - // "full" diff mode. This makes it easier to compare the result with the - // index.json file in the repo (which does not have a final newline). - process.stdout.write(JSON.stringify(diff, null, 2)); - }) - .catch(err => { - console.error(err); - process.exit(1); - }); +if (require.main === module) { + const what = process.argv[2] ?? "working"; + const diffType = process.argv[3] ?? "diff"; + + build(what, { diffType, log: console.warn }) + .then(diff => { + // Note: using process.stdout.write to avoid creating a final newline in + // "full" diff mode. This makes it easier to compare the result with the + // index.json file in the repo (which does not have a final newline). + process.stdout.write(JSON.stringify(diff, null, 2)); + }) + .catch(err => { + console.error(err); + process.exit(1); + }); +} \ No newline at end of file From 5e75cfc7657dd24b0b96082c00c47aa1ef20a3d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Daoust?= Date: Tue, 19 Mar 2024 17:50:09 +0100 Subject: [PATCH 2/2] Update src/build-diff.js Co-authored-by: Dominique Hazael-Massieux --- src/build-diff.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/build-diff.js b/src/build-diff.js index 7681c01f..b5cc352e 100644 --- a/src/build-diff.js +++ b/src/build-diff.js @@ -206,7 +206,7 @@ async function buildCommits(newRef, baseRef, { diffType = "diff", log = console. * The spec object must have a `url` property. It may have other properties. * * Internally, the function turns the parameter into a diff and hands it over - * to `buildSpec`. + * to `buildDiff`. * * The function throws in case of errors. */