Skip to content

Commit 758e77a

Browse files
committed
Update scripts to use redirects
1 parent 459ac43 commit 758e77a

File tree

8 files changed

+117
-14
lines changed

8 files changed

+117
-14
lines changed

index.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import path from 'path';
44
import { Temporal } from '@js-temporal/polyfill';
55
import { fdir } from 'fdir';
66
import YAML from 'yaml';
7-
import { FeatureData, GroupData, SnapshotData, WebFeaturesData } from './types';
7+
import { GroupData, SnapshotData, WebFeaturesData } from './types';
88

99
import { toString as hastTreeToString } from 'hast-util-to-string';
1010
import rehypeStringify from 'rehype-stringify';
@@ -14,6 +14,7 @@ import { unified } from 'unified';
1414

1515
import { BASELINE_LOW_TO_HIGH_DURATION, coreBrowserSet, parseRangedDateString } from 'compute-baseline';
1616
import { Compat } from 'compute-baseline/browser-compat-data';
17+
import { isOrdinaryFeatureData, isRedirectData } from './type-guards';
1718

1819
// The longest name allowed, to allow for compact display.
1920
const nameMaxLength = 80;
@@ -117,7 +118,7 @@ function convertMarkdown(markdown: string) {
117118
// Map from BCD keys/paths to web-features identifiers.
118119
const bcdToFeatureId: Map<string, string> = new Map();
119120

120-
const features: { [key: string]: FeatureData } = {};
121+
const features: WebFeaturesData["features"] = {};
121122
for (const [key, data] of yamlEntries('features')) {
122123
// Draft features reserve an identifier but aren't complete yet. Skip them.
123124
if (data[draft]) {
@@ -182,11 +183,34 @@ for (const [key, data] of yamlEntries('features')) {
182183
features[key] = data;
183184
}
184185

185-
// Assert that discouraged feature's alternatives are valid
186+
// Assert that feature references are valid
186187
for (const [id, feature] of Object.entries(features)) {
187-
for (const alternative of feature.discouraged?.alternatives ?? []) {
188-
if (!(alternative in features)) {
189-
throw new Error(`${id}'s alternative "${alternative}" is not a valid feature ID`);
188+
if (isOrdinaryFeatureData(feature)) {
189+
for (const alternative of feature.discouraged?.alternatives ?? []) {
190+
if (!(alternative in features)) {
191+
throw new Error(`${id}'s alternative "${alternative}" is not a valid feature ID`);
192+
}
193+
}
194+
}
195+
196+
if (isRedirectData(feature)) {
197+
const { reason } = feature.redirect;
198+
switch (reason) {
199+
case 'moved':
200+
if (!(feature.redirect.target in features)) {
201+
throw new Error(`${id}'s redirect target "${feature.redirect.target} is not a valid feature ID`);
202+
}
203+
break;
204+
case 'split':
205+
for (const target of feature.redirect.targets) {
206+
if (!(target in features)) {
207+
throw new Error(`${id}'s redirect target "${target}" is not a valid feature ID`);
208+
}
209+
}
210+
break;
211+
default:
212+
reason satisfies never;
213+
throw new Error(`Unhandled redirect reason ${reason}}`);
190214
}
191215
}
192216
}

scripts/build.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ function buildPackage() {
6868

6969
function buildExtendedJSON() {
7070
for (const [id, featureData] of Object.entries(data.features)) {
71+
if ("redirect" in featureData) {
72+
continue;
73+
}
74+
7175
if (
7276
Array.isArray(featureData.compat_features) &&
7377
featureData.compat_features.length &&

scripts/dist.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import { isDeepStrictEqual } from "node:util";
1414
import winston from "winston";
1515
import YAML, { Document, Scalar, YAMLSeq } from "yaml";
1616
import yargs from "yargs";
17+
import { isRedirectData } from "../type-guards";
18+
import {
19+
FeatureMovedData,
20+
FeatureRedirectData,
21+
FeatureSplitData,
22+
} from "../types";
1723

1824
const compat = new Compat();
1925

@@ -181,6 +187,36 @@ function compareStatus(a: SupportStatus, b: SupportStatus) {
181187
return 0;
182188
}
183189

190+
function toRedirectDist(
191+
id: string,
192+
source: FeatureMovedData | FeatureRedirectData,
193+
): YAML.Document {
194+
const dist = new Document({});
195+
196+
const comment = [
197+
`Generated from: ${id}.yml`,
198+
`This file intentionally left blank.`,
199+
`Do not edit this file.`,
200+
];
201+
202+
switch (source.redirect.reason) {
203+
case "moved":
204+
comment.push(
205+
`The data for this feature has moved to ${(source as FeatureMovedData).redirect.target}.yml`,
206+
);
207+
break;
208+
case "split":
209+
const targets = (source as FeatureSplitData).redirect.targets;
210+
comment.push(`The data for this feature has moved to:`);
211+
comment.push(...targets.map((to) => ` - ${to}.yml`));
212+
break;
213+
}
214+
215+
dist.commentBefore = comment.map((line) => ` ${line}`).join("\n");
216+
217+
return dist;
218+
}
219+
184220
/**
185221
* Generate a dist YAML document from a feature definition YAML file path.
186222
*
@@ -192,6 +228,10 @@ function toDist(sourcePath: string): YAML.Document {
192228
const source = YAML.parse(fs.readFileSync(sourcePath, { encoding: "utf-8" }));
193229
const { name: id } = path.parse(sourcePath);
194230

231+
if (isRedirectData(source)) {
232+
return toRedirectDist(id, source);
233+
}
234+
195235
// Collect tagged compat features. A `compat_features` list in the source
196236
// takes precedence, but can be removed if it matches the tagged features.
197237
const taggedCompatFeatures = (tagsToFeatures.get(`web-features:${id}`) ?? [])
Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { features } from "../index";
2+
import { isOrdinaryFeatureData } from "../type-guards";
23

34
for (const [key, data] of Object.entries(features)) {
4-
if ((data.status.baseline_low_date ?? "").includes("≤")) {
5-
console.log(key);
5+
if (isOrdinaryFeatureData(data)) {
6+
if (
7+
"status" in data &&
8+
(data.status.baseline_low_date ?? "").includes("≤")
9+
) {
10+
console.log(key);
11+
}
612
}
713
}

scripts/specs.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
33
import webSpecs from 'web-specs' with { type: 'json' };
44

55
import { features } from '../index.js';
6+
import { isRedirectData } from "../type-guards.js";
67

78
// Specs needs to be in "good standing". Nightly URLs are used if available,
89
// otherwise the snapshot/versioned URL is used. See browser-specs/web-specs
@@ -191,6 +192,10 @@ for (const [allowedUrl, message] of defaultAllowlist) {
191192
}
192193

193194
for (const [id, data] of Object.entries(features)) {
195+
if (isRedirectData(data)) {
196+
continue;
197+
}
198+
194199
const specs = Array.isArray(data.spec) ? data.spec : [data.spec];
195200
for (const spec of specs) {
196201
let url: URL;

scripts/stats.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Compat } from "compute-baseline/browser-compat-data";
22
import { fileURLToPath } from "node:url";
33
import yargs from "yargs";
44
import { features } from "../index.js";
5+
import { isOrdinaryFeatureData } from "../type-guards.js";
56

67
const argv = yargs(process.argv.slice(2))
78
.scriptName("stats")
@@ -14,11 +15,20 @@ const argv = yargs(process.argv.slice(2))
1415
}).argv;
1516

1617
export function stats(detailed: boolean = false) {
17-
const featureCount = Object.keys(features).length;
18+
const featureCount = Object.values(features).filter(
19+
isOrdinaryFeatureData,
20+
).length;
1821

1922
const keys = [];
2023
const doneKeys = Array.from(
21-
new Set(Object.values(features).flatMap((f) => f.compat_features ?? [])),
24+
new Set(
25+
Object.values(features).flatMap((f) => {
26+
if (isOrdinaryFeatureData(f)) {
27+
return f.compat_features ?? [];
28+
}
29+
return [];
30+
}),
31+
),
2232
);
2333
const toDoKeys = [];
2434

@@ -35,6 +45,7 @@ export function stats(detailed: boolean = false) {
3545
}
3646

3747
const featureSizes = Object.values(features)
48+
.filter(isOrdinaryFeatureData)
3849
.map((feature) => (feature.compat_features ?? []).length)
3950
.sort((a, b) => a - b);
4051

scripts/update-drafts.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { Document, parse } from "yaml";
1111
import yargs from "yargs";
1212

1313
import { features } from "../index.js";
14+
import { isOrdinaryFeatureData } from "../type-guards.js";
1415
import { FeatureData } from "../types.js";
1516

1617
type WebSpecsSpec = (typeof webSpecs)[number];
@@ -84,7 +85,7 @@ async function main() {
8485
// Build a map of used BCD keys to feature.
8586
const webFeatures = new Map<string, string>();
8687
Object.values(features).map((data) => {
87-
if (data.compat_features) {
88+
if (isOrdinaryFeatureData(data) && data.compat_features) {
8889
for (const compatFeature of data.compat_features) {
8990
webFeatures.set(compatFeature, data.name);
9091
}
@@ -261,9 +262,12 @@ async function main() {
261262
}
262263

263264
// Clean up completed specs, even if they've been superseded
264-
const assignedKeys = Object.values(features).flatMap(
265-
(f) => f.compat_features ?? [],
266-
);
265+
const assignedKeys = Object.values(features).flatMap((f) => {
266+
if (isOrdinaryFeatureData(f)) {
267+
return f.compat_features ?? [];
268+
}
269+
return [];
270+
});
267271
for (const spec of webSpecs) {
268272
const id = formatIdentifier(spec.shortname);
269273
const destination = `features/draft/spec/${id}.yml`;

type-guards.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { FeatureData, FeatureRedirectData } from "./types";
2+
3+
export function isOrdinaryFeatureData(x: unknown): x is FeatureData {
4+
return typeof x === "object" && "name" in x;
5+
}
6+
7+
export function isRedirectData(x: unknown): x is FeatureRedirectData {
8+
return typeof x === "object" && "redirect" in x;
9+
}

0 commit comments

Comments
 (0)