Repository navigation
Expand file tree
/
Copy pathsync.mjs
More file actions
108 lines (95 loc) · 3.87 KB
/
Copy pathsync.mjs
File metadata and controls
108 lines (95 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/**
* sync.mjs — pull the specification artefacts in from the spec repository.
*
* The site renders the normative documents rather than paraphrasing them, so
* those files live here as vendored copies and this script refreshes them. It
* is deliberately a separate step from the build: a build never reaches the
* network, so it is reproducible and cannot be broken by a push upstream.
*
* node sync.mjs # from main
* node sync.mjs v0.6.0 # from a tag
*
* Nothing about the version is typed here. The grammar version is read out of
* the schema's own `$id` and decides where the schema is written, so a release
* that moves the grammar adds a directory instead of overwriting a published
* one — those URLs are immutable (SPEC.md §10). The release version comes from
* the tag, or from the changelog when syncing an untagged branch. Both are
* recorded in content/spec-version.json, which is what lib/layout.mjs reads.
*/
import { mkdir, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
const ref = process.argv[2] ?? "main";
const base = `https://raw.githubusercontent.com/OpenPredicate/open-predicate/${ref}`;
const SCHEMA = "open-predicate-schema.json";
/** Documents and examples have fixed homes; the schema's is derived from its $id. */
const FILES = {
"SPEC.md": "content/SPEC.md",
"CHANGELOG.md": "content/CHANGELOG.md",
"COMPARISON.md": "content/COMPARISON.md",
"examples/pet.capabilities.json": "content/pet.capabilities.json",
"examples/pet.filter.json": "content/pet.filter.json",
"examples/pet.schema.json": "content/pet.schema.json",
};
async function fetchText(from) {
const url = `${base}/${from}`;
const res = await fetch(url);
if (!res.ok) throw new Error(`${res.status} ${res.statusText} — ${url}`);
return res.text();
}
async function save(to, body) {
await mkdir(dirname(to), { recursive: true });
await writeFile(to, body);
console.log(` → ${to} (${body.length} bytes)`);
}
/** The newest released version in the changelog, ignoring an Unreleased section. */
const latestChangelogVersion = (changelog) => changelog.match(/^## \[(\d+\.\d+\.\d+)\]/m)?.[1];
async function sync() {
// Fetch everything before writing anything, so a network failure halfway
// through cannot leave content/ describing two different releases.
const names = [SCHEMA, ...Object.keys(FILES)];
const bodies = Object.fromEntries(
await Promise.all(names.map(async (name) => [name, await fetchText(name)])),
);
const schema = JSON.parse(bodies[SCHEMA]);
const grammar = schema.$id?.match(/\/v(\d+\.\d+\.\d+)\//)?.[1];
if (!grammar) {
throw new Error(
`Could not read a grammar version out of the schema's $id (${schema.$id}). The $id is what ` +
`consumers pin, so the site will not guess at it.`,
);
}
const release = ref.startsWith("v")
? ref.slice(1)
: latestChangelogVersion(bodies["CHANGELOG.md"]);
if (!release) {
throw new Error(
`Could not determine a release version: ref '${ref}' is not a tag and CHANGELOG.md has no ` +
`'## [x.y.z]' heading.`,
);
}
console.log(`Syncing ${ref} — release ${release}, grammar ${grammar}\n`);
for (const [from, to] of Object.entries(FILES)) await save(to, bodies[from]);
await save(`static/schema/v${grammar}/${SCHEMA}`, bodies[SCHEMA]);
await save(
"content/spec-version.json",
`${JSON.stringify(
{
_comment: "Generated by sync.mjs. Do not edit by hand — run 'npm run sync' instead.",
ref,
release,
grammar,
schemaId: schema.$id,
},
null,
2,
)}\n`,
);
console.log(`\nSynced from ${ref}. Run 'npm test' and check the diff.`);
}
try {
await sync();
} catch (err) {
console.error(`\nSync from ${ref} failed: ${err.message}`);
console.error("Check that the ref exists, and check `git diff` before committing.");
process.exit(1);
}