Skip to content

Commit 61f5aa8

Browse files
committed
flag script
1 parent af7e8c7 commit 61f5aa8

File tree

2 files changed

+343
-1
lines changed

2 files changed

+343
-1
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,8 @@
130130
"download-build-for-head": "node ./scripts/release/download-experimental-build.js --commit=$(git rev-parse HEAD)",
131131
"download-build-in-codesandbox-ci": "cd scripts/release && yarn install && cd ../../ && yarn download-build-for-head || yarn build --type=node react/index react-dom/index react-dom/src/server react-dom/test-utils scheduler/index react/jsx-runtime react/jsx-dev-runtime",
132132
"check-release-dependencies": "node ./scripts/release/check-release-dependencies",
133-
"generate-inline-fizz-runtime": "node ./scripts/rollup/generate-inline-fizz-runtime.js"
133+
"generate-inline-fizz-runtime": "node ./scripts/rollup/generate-inline-fizz-runtime.js",
134+
"flags": "node ./scripts/flags/flags.js"
134135
},
135136
"resolutions": {
136137
"react-is": "npm:react-is"

scripts/flags/flags.js

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
'use strict';
2+
3+
const babel = require('@babel/register');
4+
const {transformSync} = require('@babel/core');
5+
const Module = require('module');
6+
const path = require('path');
7+
const fs = require('fs');
8+
babel({
9+
plugins: ['@babel/plugin-transform-modules-commonjs'],
10+
});
11+
12+
const yargs = require('yargs');
13+
const argv = yargs
14+
.parserConfiguration({
15+
// Important: This option tells yargs to move all other options not
16+
// specified here into the `_` key. We use this to send all of the
17+
// Jest options that we don't use through to Jest (like --watch).
18+
'unknown-options-as-args': true,
19+
})
20+
.wrap(yargs.terminalWidth())
21+
.options({
22+
csv: {
23+
alias: 'c',
24+
describe: 'output cvs.',
25+
requiresArg: false,
26+
type: 'boolean',
27+
default: false,
28+
},
29+
diff: {
30+
alias: 'd',
31+
describe: 'output diff of two or more flags.',
32+
requiresArg: false,
33+
type: 'array',
34+
choices: [
35+
'www',
36+
'www-modern',
37+
'rn',
38+
'rn-fb',
39+
'canary',
40+
'next',
41+
'experimental',
42+
null,
43+
],
44+
default: null,
45+
},
46+
sort: {
47+
alias: 's',
48+
describe: 'sort diff by one or more flags.',
49+
requiresArg: false,
50+
type: 'array',
51+
default: 'next',
52+
choices: [
53+
'www',
54+
'www-modern',
55+
'rn',
56+
'rn-fb',
57+
'canary',
58+
'next',
59+
'experimental',
60+
],
61+
},
62+
}).argv;
63+
64+
// Load ReactNativeFeatureFlags with __NEXT_MAJOR__ replace with 'next'.
65+
// We need to do string replace, since the __NEXT_MAJOR__ is assigned to __EXPERIMENTAL__.
66+
function getReactNativeFeatureFlagsMajor() {
67+
const virtualName = 'ReactNativeFeatureFlagsMajor.js';
68+
const file = fs.readFileSync(
69+
path.join(__dirname, '../../packages/shared/ReactFeatureFlags.js'),
70+
'utf8'
71+
);
72+
const fileContent = transformSync(
73+
file.replace(
74+
'const __NEXT_MAJOR__ = __EXPERIMENTAL__;',
75+
'const __NEXT_MAJOR__ = "next";'
76+
),
77+
{
78+
plugins: ['@babel/plugin-transform-modules-commonjs'],
79+
}
80+
).code;
81+
82+
const parent = module.parent;
83+
const m = new Module(virtualName, parent);
84+
m.filename = virtualName;
85+
86+
m._compile(fileContent, virtualName);
87+
88+
return m.exports;
89+
}
90+
91+
// The RN and www Feature flag files import files that don't exist.
92+
// Mock the imports with the dynamic flag values.
93+
function mockDynamicallyFeatureFlags() {
94+
// Mock the ReactNativeInternalFeatureFlags and ReactFeatureFlags modules
95+
const DynamicFeatureFlagsWWW = require('../../packages/shared/forks/ReactFeatureFlags.www-dynamic.js');
96+
const DynamicFeatureFlagsNative = require('../../packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js');
97+
98+
const originalLoad = Module._load;
99+
100+
Module._load = function (request, parent) {
101+
if (request === 'ReactNativeInternalFeatureFlags') {
102+
return DynamicFeatureFlagsNative;
103+
} else if (request === 'ReactFeatureFlags') {
104+
return DynamicFeatureFl agsWWW;
105+
}
106+
107+
return originalLoad.apply(this, arguments);
108+
};
109+
}
110+
// Set the globals to string values to output them to the table.
111+
global.__VARIANT__ = 'gk';
112+
global.__PROFILE__ = 'profile';
113+
global.__DEV__ = 'dev';
114+
global.__EXPERIMENTAL__ = 'experimental';
115+
116+
// Load all the feature flag files.
117+
mockDynamicallyFeatureFlags();
118+
const ReactFeatureFlags = require('../../packages/shared/ReactFeatureFlags.js');
119+
const ReactFeatureFlagsWWW = require('../../packages/shared/forks/ReactFeatureFlags.www.js');
120+
const ReactFeatureFlagsNativeFB = require('../../packages/shared/forks/ReactFeatureFlags.native-fb.js');
121+
const ReactFeatureFlagsNativeOSS = require('../../packages/shared/forks/ReactFeatureFlags.native-oss.js');
122+
const ReactFeatureFlagsMajor = getReactNativeFeatureFlagsMajor();
123+
124+
const allFlagsUniqueFlags = new Set([
125+
...Object.keys(ReactFeatureFlags),
126+
...Object.keys(ReactFeatureFlagsWWW),
127+
...Object.keys(ReactFeatureFlagsNativeFB),
128+
...Object.keys(ReactFeatureFlagsNativeOSS),
129+
]);
130+
131+
// These functions are the rules for what each value means in each channel.
132+
function getNextMajorFlagValue(flag) {
133+
const value = ReactFeatureFlagsMajor[flag];
134+
if (value === true || value === 'next') {
135+
return '✅';
136+
} else if (value === false || value === 'experimental') {
137+
return '❌';
138+
} else if (value === 'profile') {
139+
return '⏱️';
140+
} else if (value === 'dev') {
141+
return '💻';
142+
} else if (typeof value === 'number') {
143+
return value;
144+
} else {
145+
throw new Error(`Unexpected OSS Stable value ${value} for flag ${flag}`);
146+
}
147+
}
148+
149+
function getOSSCanaryFlagValue(flag) {
150+
const value = ReactFeatureFlags[flag];
151+
if (value === true) {
152+
return '✅';
153+
} else if (value === false || value === 'experimental' || value === 'next') {
154+
return '❌';
155+
} else if (value === 'profile') {
156+
return '⏱️';
157+
} else if (value === 'dev') {
158+
return '💻';
159+
} else if (typeof value === 'number') {
160+
return value;
161+
} else {
162+
throw new Error(`Unexpected OSS Canary value ${value} for flag ${flag}`);
163+
}
164+
}
165+
166+
function getOSSExperimentalFlagValue(flag) {
167+
const value = ReactFeatureFlags[flag];
168+
if (value === true || value === 'experimental') {
169+
return '✅';
170+
} else if (value === false || value === 'next') {
171+
return '❌';
172+
} else if (value === 'profile') {
173+
return '⏱️';
174+
} else if (value === 'dev') {
175+
return '💻';
176+
} else if (typeof value === 'number') {
177+
return value;
178+
} else {
179+
throw new Error(
180+
`Unexpected OSS Experimental value ${value} for flag ${flag}`
181+
);
182+
}
183+
}
184+
185+
function getWWWModernFlagValue(flag) {
186+
const value = ReactFeatureFlagsWWW[flag];
187+
if (value === true || value === 'experimental') {
188+
return '✅';
189+
} else if (value === false || value === 'next') {
190+
return '❌';
191+
} else if (value === 'profile') {
192+
return '⏱️';
193+
} else if (value === 'dev') {
194+
return '💻';
195+
} else if (value === 'gk') {
196+
return '🧪';
197+
} else if (typeof value === 'number') {
198+
return value;
199+
} else {
200+
throw new Error(`Unexpected WWW Modern value ${value} for flag ${flag}`);
201+
}
202+
}
203+
204+
function getWWWClassicFlagValue(flag) {
205+
const value = ReactFeatureFlagsWWW[flag];
206+
if (value === true) {
207+
return '✅';
208+
} else if (value === false || value === 'experimental' || value === 'next') {
209+
return '❌';
210+
} else if (value === 'profile') {
211+
return '⏱️';
212+
} else if (value === 'dev') {
213+
return '💻';
214+
} else if (value === 'gk') {
215+
return '🧪';
216+
} else if (typeof value === 'number') {
217+
return value;
218+
} else {
219+
throw new Error(`Unexpected WWW Classic value ${value} for flag ${flag}`);
220+
}
221+
}
222+
223+
function getRNOSSFlagValue(flag) {
224+
const value = ReactFeatureFlagsNativeOSS[flag];
225+
if (value === true) {
226+
return '✅';
227+
} else if (value === false || value === 'experimental' || value === 'next') {
228+
return '❌';
229+
} else if (value === 'profile') {
230+
return '⏱️';
231+
} else if (value === 'dev') {
232+
return '💻';
233+
} else if (value === 'gk') {
234+
return '🧪';
235+
} else if (typeof value === 'number') {
236+
return value;
237+
} else {
238+
throw new Error(`Unexpected RN OSS value ${value} for flag ${flag}`);
239+
}
240+
}
241+
242+
function getRNFBFlagValue(flag) {
243+
const value = ReactFeatureFlagsNativeFB[flag];
244+
if (value === true) {
245+
return '✅';
246+
} else if (value === false || value === 'experimental' || value === 'next') {
247+
return '❌';
248+
} else if (value === 'profile') {
249+
return '⏱️';
250+
} else if (value === 'dev') {
251+
return '💻';
252+
} else if (value === 'gk') {
253+
return '🧪';
254+
} else if (typeof value === 'number') {
255+
return value;
256+
} else {
257+
throw new Error(`Unexpected RN FB value ${value} for flag ${flag}`);
258+
}
259+
}
260+
261+
function argToHeader(arg) {
262+
switch (arg) {
263+
case 'www':
264+
return 'WWW Classic';
265+
case 'www-modern':
266+
return 'WWW Modern';
267+
case 'rn':
268+
return 'RN OSS';
269+
case 'rn-fb':
270+
return 'RN FB';
271+
case 'canary':
272+
return 'OSS Canary';
273+
case 'next':
274+
return 'OSS Next Major';
275+
case 'experimental':
276+
return 'OSS Experimental';
277+
default:
278+
return arg;
279+
}
280+
}
281+
282+
// Build the table with the value for each flag.
283+
const isDiff = argv.diff != null && argv.diff.length > 1;
284+
const table = {};
285+
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
286+
for (const flag of allFlagsUniqueFlags) {
287+
const values = {
288+
'OSS Next Major': getNextMajorFlagValue(flag),
289+
'OSS Canary': getOSSCanaryFlagValue(flag),
290+
'OSS Experimental': getOSSExperimentalFlagValue(flag),
291+
'WWW Classic': getWWWClassicFlagValue(flag),
292+
'WWW Modern': getWWWModernFlagValue(flag),
293+
'RN FB': getRNFBFlagValue(flag),
294+
'RN OSS': getRNOSSFlagValue(flag),
295+
};
296+
297+
if (!isDiff) {
298+
table[flag] = values;
299+
continue;
300+
}
301+
302+
const subset = argv.diff.map(argToHeader).reduce((acc, key) => {
303+
if (key in values) {
304+
acc[key] = values[key];
305+
}
306+
return acc;
307+
}, {});
308+
309+
if (new Set(Object.values(subset)).size !== 1) {
310+
table[flag] = subset;
311+
}
312+
}
313+
314+
// Sort the table
315+
const sortBy = isDiff ? argv.diff.map(argToHeader) : argv.sort.map(argToHeader);
316+
let sorted;
317+
sortBy.forEach(sort => {
318+
sorted = Object.fromEntries(
319+
Object.entries(table).sort(([, rowA], [, rowB]) =>
320+
rowB[sort].toString().localeCompare(rowA[sort])
321+
)
322+
);
323+
});
324+
325+
if (argv.csv) {
326+
const csvHeader =
327+
'Flag name, WWW Classic, RN FB, OSS Canary, OSS Experimental, WWW Modern, RN OSS\n';
328+
const csvRows = Object.keys(sorted).map(flag => {
329+
const row = sorted[flag];
330+
return `${flag}, ${row['WWW Classic']}, ${row['RN FB']}, ${row['OSS Canary']}, ${row['OSS Experimental']}, ${row['WWW Modern']}, ${row['RN OSS']}`;
331+
});
332+
fs.writeFile('./flags.csv', csvHeader + csvRows.join('\n'), function (err) {
333+
if (err) {
334+
return console.log(err);
335+
}
336+
console.log('The file was saved!');
337+
});
338+
}
339+
340+
// print table with formatting
341+
console.table(sorted);

0 commit comments

Comments
 (0)