199 lines
28 KiB
JavaScript
199 lines
28 KiB
JavaScript
"use strict";
|
|||
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
|
/**
|
||
|
|
* Generate FEATURE_FLAGS.md, a report of all current feature flags
|
||
|
|
*/
|
||
|
|
const fs_1 = require("fs");
|
||
|
|
const path = require("path");
|
||
|
|
const validate_flags_1 = require("./validate-flags");
|
||
|
|
const feats = require("../lib/features");
|
||
|
|
const flag_modeling_1 = require("../lib/private/flag-modeling");
|
||
|
|
async function main() {
|
||
|
|
(0, validate_flags_1.validateFlags)();
|
||
|
|
await updateMarkdownFile(path.join(__dirname, '..', 'FEATURE_FLAGS.md'), {
|
||
|
|
table: flagsTable(),
|
||
|
|
details: flagsDetails(),
|
||
|
|
json: recommendedJson(),
|
||
|
|
removed: removedFlags(),
|
||
|
|
diff: changedFlags(),
|
||
|
|
migratejson: migrateJson(),
|
||
|
|
});
|
||
|
|
// Write to the package root
|
||
|
|
await updateRecommendedFlagsFile(path.join(__dirname, '..', '..', 'recommended-feature-flags.json'));
|
||
|
|
}
|
||
|
|
function flagsTable() {
|
||
|
|
return renderTable([
|
||
|
|
['Flag', 'Summary', 'Since', 'Type'],
|
||
|
|
...v2flags().map(([name, flag]) => [
|
||
|
|
renderLink(mdEsc(name), githubHeadingLink(flagDetailsHeading(name, flag))),
|
||
|
|
flag.summary,
|
||
|
|
flag.introducedIn.v2 ?? '',
|
||
|
|
renderType(flag.type, 'short'),
|
||
|
|
]),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
function removedFlags() {
|
||
|
|
const removedInV2 = flags(flag => flag.introducedIn.v2 === undefined && flag.introducedIn.v1 !== undefined);
|
||
|
|
return renderTable([
|
||
|
|
['Flag', 'Summary', 'Type', 'Since'],
|
||
|
|
...removedInV2.map(([name, flag]) => [
|
||
|
|
renderLink(mdEsc(name), githubHeadingLink(flagDetailsHeading(name, flag))),
|
||
|
|
flag.summary,
|
||
|
|
renderType(flag.type, 'short'),
|
||
|
|
flag.introducedIn.v1 ?? '',
|
||
|
|
]),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
function changedFlags() {
|
||
|
|
const changedInV2 = flags(flag => !!flag.unconfiguredBehavesLike?.v2 && !!flag.introducedIn.v2);
|
||
|
|
return renderTable([
|
||
|
|
['Flag', 'Summary', 'Type', 'Since', 'v1 default', 'v2 default'],
|
||
|
|
...changedInV2.map(([name, flag]) => [
|
||
|
|
renderLink(mdEsc(name), githubHeadingLink(flagDetailsHeading(name, flag))),
|
||
|
|
flag.summary,
|
||
|
|
renderType(flag.type, 'short'),
|
||
|
|
flag.introducedIn.v1 ?? '',
|
||
|
|
renderValue(false),
|
||
|
|
renderValue(flag.unconfiguredBehavesLike?.v2),
|
||
|
|
]),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
function migrateJson() {
|
||
|
|
const changedInV2 = flags(flag => !!flag.unconfiguredBehavesLike?.v2 && !!flag.introducedIn.v2 && !!flag.introducedIn.v1);
|
||
|
|
const context = Object.fromEntries(changedInV2.map(([name, _]) => [name, false]));
|
||
|
|
return [
|
||
|
|
'```json',
|
||
|
|
JSON.stringify({ context }, undefined, 2),
|
||
|
|
'```',
|
||
|
|
].join('\n');
|
||
|
|
}
|
||
|
|
function flagsDetails() {
|
||
|
|
const allFlags = flags(_ => true);
|
||
|
|
return allFlags.flatMap(([name, flag]) => [
|
||
|
|
`### ${flagDetailsHeading(name, flag)}`,
|
||
|
|
'',
|
||
|
|
`*${flag.summary}*`,
|
||
|
|
'',
|
||
|
|
`Flag type: ${renderType(flag.type, 'long')}`,
|
||
|
|
'',
|
||
|
|
dedent(flag.detailsMd),
|
||
|
|
'',
|
||
|
|
renderTable([
|
||
|
|
['Since', 'Unset behaves like', 'Recommended value'],
|
||
|
|
// V1
|
||
|
|
flag.introducedIn.v1
|
||
|
|
? [flag.introducedIn.v1, renderValue(false), renderValue(flag.recommendedValue)]
|
||
|
|
: ['(not in v1)', '', ''],
|
||
|
|
// V2
|
||
|
|
flag.introducedIn.v2
|
||
|
|
? [flag.introducedIn.v2, renderValue(flag.unconfiguredBehavesLike?.v2 ?? false), renderValue(flag.recommendedValue)]
|
||
|
|
: flag.unconfiguredBehavesLike?.v2 !== undefined
|
||
|
|
? ['(not configurable in v2)', renderValue(flag.unconfiguredBehavesLike?.v2), '']
|
||
|
|
: ['(not in v2)', '', ''],
|
||
|
|
]),
|
||
|
|
...oldBehavior(flag) ? [
|
||
|
|
`**Compatibility with old behavior:** ${oldBehavior(flag)}`,
|
||
|
|
'',
|
||
|
|
] : [],
|
||
|
|
'',
|
||
|
|
]).join('\n');
|
||
|
|
}
|
||
|
|
function oldBehavior(flag) {
|
||
|
|
switch (flag.type) {
|
||
|
|
case flag_modeling_1.FlagType.ApiDefault: return flag.compatibilityWithOldBehaviorMd;
|
||
|
|
case flag_modeling_1.FlagType.BugFix: return flag.compatibilityWithOldBehaviorMd;
|
||
|
|
case flag_modeling_1.FlagType.VisibleContext: return undefined;
|
||
|
|
case flag_modeling_1.FlagType.Temporary: return flag.compatibilityWithOldBehaviorMd;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function recommendedJson() {
|
||
|
|
const sortedFlags = Object.fromEntries(Object.entries(feats.CURRENTLY_RECOMMENDED_FLAGS).sort(([a], [b]) => a.localeCompare(b)));
|
||
|
|
return [
|
||
|
|
'```json',
|
||
|
|
JSON.stringify({ context: sortedFlags }, undefined, 2),
|
||
|
|
'```',
|
||
|
|
].join('\n');
|
||
|
|
}
|
||
|
|
function v2flags() {
|
||
|
|
return flags(flag => flag.introducedIn.v2 !== undefined);
|
||
|
|
}
|
||
|
|
function flags(pred) {
|
||
|
|
const entries = Object.entries(feats.FLAGS)
|
||
|
|
.filter(([_, flag]) => pred(flag));
|
||
|
|
entries.sort((a, b) => firstCmp(
|
||
|
|
// Sort by versions first
|
||
|
|
(0, flag_modeling_1.compareVersions)(a[1].introducedIn.v2, b[1].introducedIn.v2), (0, flag_modeling_1.compareVersions)(a[1].introducedIn.v1, b[1].introducedIn.v1),
|
||
|
|
// Then sort by name
|
||
|
|
a[0].localeCompare(b[0])));
|
||
|
|
return entries;
|
||
|
|
}
|
||
|
|
function renderType(type, flavor) {
|
||
|
|
switch (type) {
|
||
|
|
case flag_modeling_1.FlagType.ApiDefault: return longShort('New default behavior', 'new default');
|
||
|
|
case flag_modeling_1.FlagType.BugFix: return longShort('Backwards incompatible bugfix', 'fix');
|
||
|
|
case flag_modeling_1.FlagType.VisibleContext: return longShort('Configuration option', 'config');
|
||
|
|
case flag_modeling_1.FlagType.Temporary: return longShort('Temporary flag', 'temporary');
|
||
|
|
}
|
||
|
|
function longShort(long, short) {
|
||
|
|
return flavor === 'long' ? long : short;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function renderTable(rows) {
|
||
|
|
return [
|
||
|
|
'',
|
||
|
|
'| ' + rows[0].join(' | ') + ' |',
|
||
|
|
'| ' + rows[0].map(_ => '-----').join(' | ') + ' |',
|
||
|
|
...rows.slice(1).map(row => '| ' + row.join(' | ') + ' |'),
|
||
|
|
'',
|
||
|
|
].join('\n');
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Return the heading that will be used to caption this flag's details
|
||
|
|
*/
|
||
|
|
function flagDetailsHeading(name, _) {
|
||
|
|
return name;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Return a link that is valid on GitHub to refer to a heading
|
||
|
|
*/
|
||
|
|
function githubHeadingLink(heading) {
|
||
|
|
return `#${heading.toLowerCase().replace(/ /g, '-').replace(/[^a-z0-9_-]/g, '')}`;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Remove shared leading whitespace from all non-empty lines
|
||
|
|
*/
|
||
|
|
function dedent(body) {
|
||
|
|
const lines = body.split('\n').filter((x) => x.trim() !== '');
|
||
|
|
const leadingWs = lines.map(x => x.match(/^ */)?.[0].length ?? 0);
|
||
|
|
const sharedWs = Math.min(...leadingWs);
|
||
|
|
const re = new RegExp('^' + ' '.repeat(sharedWs), 'mg');
|
||
|
|
return body.replace(re, '').trim();
|
||
|
|
}
|
||
|
|
function renderValue(x) {
|
||
|
|
return `\`${JSON.stringify(x)}\``;
|
||
|
|
}
|
||
|
|
function renderLink(caption, link) {
|
||
|
|
return `[${caption}](${link})`;
|
||
|
|
}
|
||
|
|
function mdEsc(x) {
|
||
|
|
return x.replace(/_/g, '\\_');
|
||
|
|
}
|
||
|
|
async function updateMarkdownFile(filename, sections) {
|
||
|
|
let contents = await fs_1.promises.readFile(filename, { encoding: 'utf-8' });
|
||
|
|
for (const [section, value] of Object.entries(sections)) {
|
||
|
|
const re = new RegExp(`<!-- BEGIN ${section} -->(.*)<!-- END ${section} -->`, 's');
|
||
|
|
contents = contents.replace(re, `<!-- BEGIN ${section} -->\n${value}\n<!-- END ${section} -->`);
|
||
|
|
}
|
||
|
|
await fs_1.promises.writeFile(filename, contents, { encoding: 'utf-8' });
|
||
|
|
}
|
||
|
|
async function updateRecommendedFlagsFile(filename) {
|
||
|
|
await fs_1.promises.writeFile(filename, JSON.stringify(feats.CURRENTLY_RECOMMENDED_FLAGS, undefined, 2), { encoding: 'utf-8' });
|
||
|
|
}
|
||
|
|
function firstCmp(...xs) {
|
||
|
|
return xs.find(x => x !== 0) ?? 0;
|
||
|
|
}
|
||
|
|
main().catch(e => {
|
||
|
|
console.error(e);
|
||
|
|
process.exitCode = 1;
|
||
|
|
});
|
||
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmxhZy1yZXBvcnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJmbGFnLXJlcG9ydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBOztHQUVHO0FBQ0gsMkJBQW9DO0FBQ3BDLDZCQUE2QjtBQUM3QixxREFBaUQ7QUFDakQseUNBQXlDO0FBRXpDLGdFQUF5RTtBQUV6RSxLQUFLLFVBQVUsSUFBSTtJQUNqQixJQUFBLDhCQUFhLEdBQUUsQ0FBQztJQUVoQixNQUFNLGtCQUFrQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxFQUFFO1FBQ3ZFLEtBQUssRUFBRSxVQUFVLEVBQUU7UUFDbkIsT0FBTyxFQUFFLFlBQVksRUFBRTtRQUN2QixJQUFJLEVBQUUsZUFBZSxFQUFFO1FBQ3ZCLE9BQU8sRUFBRSxZQUFZLEVBQUU7UUFDdkIsSUFBSSxFQUFFLFlBQVksRUFBRTtRQUNwQixXQUFXLEVBQUUsV0FBVyxFQUFFO0tBQzNCLENBQUMsQ0FBQztJQUVILDRCQUE0QjtJQUM1QixNQUFNLDBCQUEwQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsZ0NBQWdDLENBQUMsQ0FBQyxDQUFDO0FBQ3ZHLENBQUM7QUFFRCxTQUFTLFVBQVU7SUFDakIsT0FBTyxXQUFXLENBQUM7UUFDakIsQ0FBQyxNQUFNLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxNQUFNLENBQUM7UUFDcEMsR0FBRyxPQUFPLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsRUFBRSxFQUFFLENBQ2hDO1lBQ0UsVUFBVSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRSxpQkFBaUIsQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUMxRSxJQUFJLENBQUMsT0FBTztZQUNaLElBQUksQ0FBQyxZ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
|