Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 23 additions & 10 deletions docs/register/register.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@

var REPO = "OpenTechEvents/opentechevents-spec";
var ISSUE_TEMPLATE = "adopter.yml";
var SCHEMA_BASE = "../schema/v0.4/"; // published copies — same origin, no CORS
/* Published schema copies — same origin, no CORS. The required-field lists are read
from the version the feed itself declares: checking a valid 0.3 feed against 0.4's
lists would report a problem its publisher has no reason to consider one. Unknown
or absent version falls back to the newest. */
var SCHEMA_VERSIONS = ["0.1.0", "0.2.0", "0.3.0", "0.4.0"];
var LATEST_SCHEMA = SCHEMA_VERSIONS[SCHEMA_VERSIONS.length - 1];
function schemaBase(version) {
var known = SCHEMA_VERSIONS.indexOf(version) !== -1 ? version : LATEST_SCHEMA;
return "../schema/v" + known.split(".").slice(0, 2).join(".") + "/";
}
var REGISTRY = "../data/adopters.json"; // the same file the site's adopter list renders from

/* ---------- linking sources (pluggable) ----------
Expand Down Expand Up @@ -296,20 +305,22 @@
var feedStatus = document.getElementById("reg-feed-status");
var requiredFields = null; // { feed: [...], event: [...] } from the published schemas

function loadRequired() {
if (requiredFields) return Promise.resolve(requiredFields);
function loadRequired(version) {
var base = schemaBase(version);
if (requiredFields && requiredFields.base === base) return Promise.resolve(requiredFields);
return Promise.all([
fetch(SCHEMA_BASE + "feed.schema.json").then(function (r) { return r.json(); }),
fetch(SCHEMA_BASE + "event.schema.json").then(function (r) { return r.json(); }),
fetch(base + "feed.schema.json").then(function (r) { return r.json(); }),
fetch(base + "event.schema.json").then(function (r) { return r.json(); }),
]).then(function (schemas) {
requiredFields = {
base: base,
feed: schemas[0].required || [],
event: (schemas[1].$defs && schemas[1].$defs.event && schemas[1].$defs.event.required) || [],
};
return requiredFields;
}).catch(function () {
// Schemas unreachable (offline dev?): degrade to the structural checks only.
requiredFields = { feed: [], event: [] };
requiredFields = { base: base, feed: [], event: [] };
return requiredFields;
});
}
Expand Down Expand Up @@ -355,12 +366,14 @@
renderFeedStatus();
};

Promise.all([fetch(url), loadRequired()]).then(function (results) {
var res = results[0], req = results[1];
fetch(url).then(function (res) {
if (!res.ok) return done("invalid", [{ key: "errHttp", vars: { status: String(res.status) } }]);
return res.json().then(function (doc) {
var errors = structuralErrors(doc, req);
done(errors.length ? "invalid" : "valid", errors);
// The feed's own version decides which required-field lists it is measured by.
return loadRequired(doc.specVersion).then(function (req) {
var errors = structuralErrors(doc, req);
done(errors.length ? "invalid" : "valid", errors);
});
}, function () {
done("invalid", [{ key: "errJson", vars: {} }]);
});
Expand Down
87 changes: 81 additions & 6 deletions scripts/register-adopter.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
* Outputs (to $GITHUB_OUTPUT when set, stdout otherwise):
* valid=true|false whether the feed validated
* name=<community> for commit message / PR title
* spec=v0.4 the spec version this run measured against, for the PR body
* spec=v0.3 the spec version this run measured against — the one the feed
* declares, not necessarily the newest — for the PR body
* validator=<url> the feed's permalink in the online validator, for the PR body
* A human-readable report is written to $REPORT_PATH (default: adopter-report.md)
* — that file becomes the comment on the issue, valid or not. It opens with a hidden
Expand All @@ -22,14 +23,28 @@
* An invalid feed is a normal outcome, not a job failure: the script only exits
* non-zero when it cannot do its work (missing fields, unreadable adopters.json).
*/
import { readFileSync, writeFileSync, appendFileSync } from "node:fs";
import { readFileSync, writeFileSync, appendFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import Ajv2020 from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import { annotationKeywords, customFormats, customKeywords } from "../index.js";

const SPEC = "spec/v0.4";
const SPEC_VERSION = SPEC.split("/").pop(); // "v0.4" — the version every message quotes
/* A feed is measured against the version it declares, not against the newest one:
every published version keeps its schemas here, and telling someone whose valid
0.3 feed we asked to register that it "does not validate" — because of the
specVersion const and nothing else — is a rejection at the front door for a feed
that was never broken. Older than the support window is a different matter: there
we do ask for a migration, and say so. Same rule as scripts/check-feeds.mjs. */
const SUPPORT_WINDOW = Number(process.env.FEED_SUPPORT_WINDOW || 3);

const versions = readdirSync("spec")
.filter((d) => /^v\d+\.\d+$/.test(d))
.map((dir) => ({ dir, minor: dir.slice(1).split(".").map(Number) }))
.sort((a, b) => a.minor[0] - b.minor[0] || a.minor[1] - b.minor[1]);
const byVersion = new Map(versions.map((v) => [`${v.minor[0]}.${v.minor[1]}.0`, v.dir]));
const LATEST = [...byVersion.keys()].at(-1);
const supported = [...byVersion.keys()].slice(-SUPPORT_WINDOW);

const ADOPTERS = join("docs", "data", "adopters.json");
const REPORT = process.env.REPORT_PATH || "adopter-report.md";

Expand Down Expand Up @@ -94,7 +109,6 @@ if (!body) {

const fields = parseIssue(body);
setOutput("name", fields.name || "");
setOutput("spec", SPEC_VERSION);

if (!fields.name || !fields.feed) {
report(false, [
Expand All @@ -114,9 +128,20 @@ setOutput("validator", validatorLink(fields.feed));
const ORIGIN = "https://opentechevents.org";
let cors = null;

async function fetchFeed(url) {
try {
return await fetch(url, { headers: { Origin: ORIGIN }, signal: AbortSignal.timeout(60000) });
} catch (err) {
await new Promise((r) => setTimeout(r, 3000));
return fetch(url, { headers: { Origin: ORIGIN }, signal: AbortSignal.timeout(60000) });
}
}

let doc;
try {
const res = await fetch(fields.feed, { headers: { Origin: ORIGIN }, signal: AbortSignal.timeout(20000) });
// Generous, and retried once: a feed that takes 30s to build half a megabyte is slow,
// not broken, and a timeout here reads to the publisher as "your feed is wrong".
const res = await fetchFeed(fields.feed);
if (!res.ok) throw new Error(`the URL answered HTTP ${res.status}`);
const acao = res.headers.get("access-control-allow-origin");
if (acao !== "*") cors = acao ? `only allows reads from \`${acao}\`` : "sends no `Access-Control-Allow-Origin`";
Expand All @@ -136,6 +161,48 @@ try {
process.exit(0);
}

/* Which rules to measure this feed by. Absent or unknown means nobody can judge it:
there is no schema for a version this project never published. Older than the
support window is the one case where being behind really is the problem, and the
report says so instead of dressing it up as a validation error. */
const declared = typeof doc.specVersion === "string" ? doc.specVersion : null;

if (!declared || !byVersion.has(declared)) {
report(false, [
"### ❌ Unknown `specVersion`",
"",
declared
? `The feed declares \`specVersion: "${declared}"\`, which is not a published version of OTE Spec.`
: "The feed does not declare a `specVersion`, so there are no rules to check it against.",
"",
`Published versions: ${[...byVersion.keys()].map((v) => `\`${v}\``).join(", ")}. The current one is \`${LATEST}\`.`,
"",
`👉 **[Open your feed in the OTE validator](${validatorLink(fields.feed)})**. The [field reference](https://opentechevents.org/spec/) documents every field.`,
"",
RETRY,
]);
process.exit(0);
}

if (!supported.includes(declared)) {
report(false, [
`### ❌ OTE Spec ${declared} is no longer supported`,
"",
`The registry tracks the last ${supported.length} versions (${supported.map((v) => `\`${v}\``).join(", ")}); this feed declares \`${declared}\`.`,
"",
`Its schemas are still in the repo under [\`spec/${byVersion.get(declared)}\`](https://github.com/OpenTechEvents/opentechevents-spec/tree/main/spec/${byVersion.get(declared)}), so nothing you publish stops working — but to be listed, please move the feed to \`${LATEST}\`.`,
"",
`👉 **[Open your feed in the OTE validator](${validatorLink(fields.feed)})** once you've migrated.`,
"",
RETRY,
]);
process.exit(0);
}

const SPEC = join("spec", byVersion.get(declared));
const SPEC_VERSION = byVersion.get(declared); // "v0.3" — the version every message quotes
setOutput("spec", SPEC_VERSION);

// Same Ajv setup as scripts/validate.mjs — see the comment there about strictRequired.
const ajv = new Ajv2020({ strict: true, strictRequired: false, allErrors: true });
addFormats(ajv);
Expand Down Expand Up @@ -192,6 +259,14 @@ report(true, [
"A pull request adding the community to the adopters registry follows; a maintainer will review and merge it.",
"",
`Keep this link to re-check the feed whenever you change it: [validator.opentechevents.org](${validatorLink(fields.feed)}).`,
// Being behind is not a defect and does not hold up the registration: said once, as a
// note, so nobody reads a supported version as a problem they have to fix today.
...(declared !== LATEST
? [
"",
`> ℹ️ This feed declares \`${declared}\` and the current version is \`${LATEST}\`. Both are supported and it changes nothing here — whenever you feel like migrating, the [changelog](https://opentechevents.org/history/) lists what moved.`,
]
: []),
...(cors
? [
"",
Expand Down
Loading