'use strict';
const babel = require('@babel/register');
const {transformSync} = require('@babel/core');
const Module = require('module');
const path = require('path');
const fs = require('fs');
babel({
plugins: ['@babel/plugin-transform-modules-commonjs'],
});
const yargs = require('yargs');
const argv = yargs
.parserConfiguration({
'unknown-options-as-args': true,
})
.wrap(yargs.terminalWidth())
.options({
csv: {
alias: 'c',
describe: 'output cvs.',
requiresArg: false,
type: 'boolean',
default: false,
},
diff: {
alias: 'd',
describe: 'output diff of two or more flags.',
requiresArg: false,
type: 'array',
choices: [
'www',
'www-modern',
'rn',
'rn-fb',
'rn-next',
'canary',
'next',
'experimental',
null,
],
default: null,
},
sort: {
alias: 's',
describe: 'sort diff by one or more flags.',
requiresArg: false,
type: 'string',
default: 'flag',
choices: [
'flag',
'www',
'www-modern',
'rn',
'rn-fb',
'rn-next',
'canary',
'next',
'experimental',
],
},
}).argv;
function getReactFeatureFlagsMajor() {
const virtualName = 'ReactFeatureFlagsMajor.js';
const file = fs.readFileSync(
path.join(__dirname, '../../packages/shared/ReactFeatureFlags.js'),
'utf8'
);
const fileContent = transformSync(
file.replace(
'const __NEXT_MAJOR__ = __EXPERIMENTAL__;',
'const __NEXT_MAJOR__ = "next";'
),
{
plugins: ['@babel/plugin-transform-modules-commonjs'],
}
).code;
const parent = module.parent;
const m = new Module(virtualName, parent);
m.filename = virtualName;
m._compile(fileContent, virtualName);
return m.exports;
}
function getReactNativeFeatureFlagsMajor() {
const virtualName = 'ReactNativeFeatureFlagsMajor.js';
const file = fs.readFileSync(
path.join(
__dirname,
'../../packages/shared/forks/ReactFeatureFlags.native-oss.js'
),
'utf8'
);
const fileContent = transformSync(
file
.replace(
'const __NEXT_RN_MAJOR__ = true;',
'const __NEXT_RN_MAJOR__ = "next";'
)
.replace(
'const __TODO_NEXT_RN_MAJOR__ = false;',
'const __TODO_NEXT_RN_MAJOR__ = "next-todo";'
),
{
plugins: ['@babel/plugin-transform-modules-commonjs'],
}
).code;
const parent = module.parent;
const m = new Module(virtualName, parent);
m.filename = virtualName;
m._compile(fileContent, virtualName);
return m.exports;
}
function mockDynamicallyFeatureFlags() {
const DynamicFeatureFlagsWWW = require('../../packages/shared/forks/ReactFeatureFlags.www-dynamic.js');
const DynamicFeatureFlagsNative = require('../../packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js');
const originalLoad = Module._load;
Module._load = function (request, parent) {
if (request === 'ReactNativeInternalFeatureFlags') {
return DynamicFeatureFlagsNative;
} else if (request === 'ReactFeatureFlags') {
return DynamicFeatureFlagsWWW;
}
return originalLoad.apply(this, arguments);
};
}
global.__VARIANT__ = 'gk';
global.__PROFILE__ = 'profile';
global.__DEV__ = 'dev';
global.__EXPERIMENTAL__ = 'experimental';
mockDynamicallyFeatureFlags();
const ReactFeatureFlags = require('../../packages/shared/ReactFeatureFlags.js');
const ReactFeatureFlagsWWW = require('../../packages/shared/forks/ReactFeatureFlags.www.js');
const ReactFeatureFlagsNativeFB = require('../../packages/shared/forks/ReactFeatureFlags.native-fb.js');
const ReactFeatureFlagsMajor = getReactFeatureFlagsMajor();
const ReactNativeFeatureFlagsMajor = getReactNativeFeatureFlagsMajor();
const allFlagsUniqueFlags = Array.from(
new Set([
...Object.keys(ReactFeatureFlags),
...Object.keys(ReactFeatureFlagsWWW),
...Object.keys(ReactFeatureFlagsNativeFB),
])
).sort();
function getNextMajorFlagValue(flag) {
const value = ReactFeatureFlagsMajor[flag];
if (value === true || value === 'next') {
return 'โ
';
} else if (value === false || value === null || value === 'experimental') {
return 'โ';
} else if (value === 'profile') {
return '๐';
} else if (value === 'dev') {
return '๐ป';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(`Unexpected OSS Stable value ${value} for flag ${flag}`);
}
}
function getOSSCanaryFlagValue(flag) {
const value = ReactFeatureFlags[flag];
if (value === true) {
return 'โ
';
} else if (
value === false ||
value === null ||
value === 'experimental' ||
value === 'next'
) {
return 'โ';
} else if (value === 'profile') {
return '๐';
} else if (value === 'dev') {
return '๐ป';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(`Unexpected OSS Canary value ${value} for flag ${flag}`);
}
}
function getOSSExperimentalFlagValue(flag) {
const value = ReactFeatureFlags[flag];
if (value === true || value === 'experimental') {
return 'โ
';
} else if (value === false || value === null || value === 'next') {
return 'โ';
} else if (value === 'profile') {
return '๐';
} else if (value === 'dev') {
return '๐ป';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(
`Unexpected OSS Experimental value ${value} for flag ${flag}`
);
}
}
function getWWWModernFlagValue(flag) {
const value = ReactFeatureFlagsWWW[flag];
if (value === true || value === 'experimental') {
return 'โ
';
} else if (value === false || value === null || value === 'next') {
return 'โ';
} else if (value === 'profile') {
return '๐';
} else if (value === 'dev') {
return '๐ป';
} else if (value === 'gk') {
return '๐งช';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(`Unexpected WWW Modern value ${value} for flag ${flag}`);
}
}
function getWWWClassicFlagValue(flag) {
const value = ReactFeatureFlagsWWW[flag];
if (value === true) {
return 'โ
';
} else if (
value === false ||
value === null ||
value === 'experimental' ||
value === 'next'
) {
return 'โ';
} else if (value === 'profile') {
return '๐';
} else if (value === 'dev') {
return '๐ป';
} else if (value === 'gk') {
return '๐งช';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(`Unexpected WWW Classic value ${value} for flag ${flag}`);
}
}
function getRNNextMajorFlagValue(flag) {
const value = ReactNativeFeatureFlagsMajor[flag];
if (value === true || value === 'next') {
return 'โ
';
} else if (value === 'next-todo') {
return '๐';
} else if (value === false || value === null || value === 'experimental') {
return 'โ';
} else if (value === 'profile') {
return '๐';
} else if (value === 'dev') {
return '๐ป';
} else if (value === 'gk') {
return '๐งช';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(`Unexpected RN OSS value ${value} for flag ${flag}`);
}
}
function getRNOSSFlagValue(flag) {
const value = ReactNativeFeatureFlagsMajor[flag];
if (value === true) {
return 'โ
';
} else if (
value === false ||
value === null ||
value === 'experimental' ||
value === 'next' ||
value === 'next-todo'
) {
return 'โ';
} else if (value === 'profile') {
return '๐';
} else if (value === 'dev') {
return '๐ป';
} else if (value === 'gk') {
return '๐งช';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(`Unexpected RN OSS value ${value} for flag ${flag}`);
}
}
function getRNFBFlagValue(flag) {
const value = ReactFeatureFlagsNativeFB[flag];
if (value === true) {
return 'โ
';
} else if (
value === false ||
value === null ||
value === 'experimental' ||
value === 'next'
) {
return 'โ';
} else if (value === 'profile') {
return '๐';
} else if (value === 'dev') {
return '๐ป';
} else if (value === 'gk') {
return '๐งช';
} else if (typeof value === 'number') {
return value;
} else {
throw new Error(`Unexpected RN FB value ${value} for flag ${flag}`);
}
}
function argToHeader(arg) {
switch (arg) {
case 'www':
return 'WWW Classic';
case 'www-modern':
return 'WWW Modern';
case 'rn':
return 'RN OSS';
case 'rn-fb':
return 'RN FB';
case 'rn-next':
return 'RN Next Major';
case 'canary':
return 'OSS Canary';
case 'next':
return 'OSS Next Major';
case 'experimental':
return 'OSS Experimental';
default:
return arg;
}
}
const FLAG_CONFIG = {
'OSS Next Major': getNextMajorFlagValue,
'OSS Canary': getOSSCanaryFlagValue,
'OSS Experimental': getOSSExperimentalFlagValue,
'WWW Classic': getWWWClassicFlagValue,
'WWW Modern': getWWWModernFlagValue,
'RN FB': getRNFBFlagValue,
'RN OSS': getRNOSSFlagValue,
'RN Next Major': getRNNextMajorFlagValue,
};
const FLAG_COLUMNS = Object.keys(FLAG_CONFIG);
const isDiff = argv.diff != null && argv.diff.length > 1;
const table = {};
for (const flag of allFlagsUniqueFlags) {
const values = FLAG_COLUMNS.reduce((acc, key) => {
acc[key] = FLAG_CONFIG[key](flag);
return acc;
}, {});
if (!isDiff) {
table[flag] = values;
continue;
}
const subset = argv.diff.map(argToHeader).reduce((acc, key) => {
if (key in values) {
acc[key] = values[key];
}
return acc;
}, {});
if (new Set(Object.values(subset)).size !== 1) {
table[flag] = subset;
}
}
let sorted = table;
if (isDiff || argv.sort) {
const sortChannel = argToHeader(isDiff ? argv.diff[0] : argv.sort);
const sortBy =
sortChannel === 'flag'
? ([flagA], [flagB]) => {
return flagA.localeCompare(flagB);
}
: ([, rowA], [, rowB]) => {
return rowB[sortChannel].toString().localeCompare(rowA[sortChannel]);
};
sorted = Object.fromEntries(Object.entries(table).sort(sortBy));
}
if (argv.csv) {
const csvRows = [
`Flag name, ${FLAG_COLUMNS.join(', ')}`,
...Object.keys(table).map(flag => {
const row = sorted[flag];
return `${flag}, ${FLAG_COLUMNS.map(col => row[col]).join(', ')}`;
}),
];
fs.writeFile('./flags.csv', csvRows.join('\n'), function (err) {
if (err) {
return console.log(err);
}
console.log('The file was saved to ./flags.csv');
});
}
const maxLength = Math.max(...Object.keys(sorted).map(item => item.length));
const padded = {};
Object.keys(sorted).forEach(key => {
const newKey = key.padEnd(maxLength, ' ');
padded[newKey] = sorted[key];
});
console.table(padded);
console.log(`
Legend:
โ
On
โ Off
๐ป DEV
๐ TODO
๐ Profiling
๐งช Experiment
`);