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: 'string',
51
+ default: 'flag',
52
+ choices: [
53
+ 'flag',
54
+ 'www',
55
+ 'www-modern',
56
+ 'rn',
57
+ 'rn-fb',
58
+ 'canary',
59
+ 'next',
60
+ 'experimental',
61
+ ],
62
+ },
63
+ }).argv;
64
+
65
+// Load ReactNativeFeatureFlags with __NEXT_MAJOR__ replace with 'next'.
66
+// We need to do string replace, since the __NEXT_MAJOR__ is assigned to __EXPERIMENTAL__.
67
+function getReactNativeFeatureFlagsMajor() {
68
+ const virtualName = 'ReactNativeFeatureFlagsMajor.js';
69
+ const file = fs.readFileSync(
70
+ path.join(__dirname, '../../packages/shared/ReactFeatureFlags.js'),
71
+ 'utf8'
72
+ );
73
+ const fileContent = transformSync(
74
+ file.replace(
75
+ 'const __NEXT_MAJOR__ = __EXPERIMENTAL__;',
76
+ 'const __NEXT_MAJOR__ = "next";'
77
+ ),
78
+ {
79
+ plugins: ['@babel/plugin-transform-modules-commonjs'],
80
+ }
81
+ ).code;
82
+
83
+ const parent = module.parent;
84
+ const m = new Module(virtualName, parent);
85
+ m.filename = virtualName;
86
+
87
+ m._compile(fileContent, virtualName);
88
+
89
+ return m.exports;
90
+}
91
+
92
+// The RN and www Feature flag files import files that don't exist.
93
+// Mock the imports with the dynamic flag values.
94
+function mockDynamicallyFeatureFlags() {
95
+ // Mock the ReactNativeInternalFeatureFlags and ReactFeatureFlags modules
96
+ const DynamicFeatureFlagsWWW = require('../../packages/shared/forks/ReactFeatureFlags.www-dynamic.js');
97
+ const DynamicFeatureFlagsNative = require('../../packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js');
98
+
99
+ const originalLoad = Module._load;
100
+
101
+ Module._load = function (request, parent) {
102
+ if (request === 'ReactNativeInternalFeatureFlags') {
103
+ return DynamicFeatureFlagsNative;
104
+ } else if (request === 'ReactFeatureFlags') {
105
+ return DynamicFeatureFlagsWWW;
106
+ }
107
+
108
+ return originalLoad.apply(this, arguments);
109
+ };
110
+}
111
+// Set the globals to string values to output them to the table.
112
+global.__VARIANT__ = 'gk';
113
+global.__PROFILE__ = 'profile';
114
+global.__DEV__ = 'dev';
115
+global.__EXPERIMENTAL__ = 'experimental';
116
+
117
+// Load all the feature flag files.
118
+mockDynamicallyFeatureFlags();
119
+const ReactFeatureFlags = require('../../packages/shared/ReactFeatureFlags.js');
120
+const ReactFeatureFlagsWWW = require('../../packages/shared/forks/ReactFeatureFlags.www.js');
121
+const ReactFeatureFlagsNativeFB = require('../../packages/shared/forks/ReactFeatureFlags.native-fb.js');
122
+const ReactFeatureFlagsNativeOSS = require('../../packages/shared/forks/ReactFeatureFlags.native-oss.js');
123
+const ReactFeatureFlagsMajor = getReactNativeFeatureFlagsMajor();
124
+
125
+const allFlagsUniqueFlags = Array.from(
126
+ new Set([
127
+ ...Object.keys(ReactFeatureFlags),
128
+ ...Object.keys(ReactFeatureFlagsWWW),
129
+ ...Object.keys(ReactFeatureFlagsNativeFB),
130
+ ...Object.keys(ReactFeatureFlagsNativeOSS),
131
+ ])
132
+).sort();
133
+
134
+// These functions are the rules for what each value means in each channel.
135
+function getNextMajorFlagValue(flag) {
136
+ const value = ReactFeatureFlagsMajor[flag];
137
+ if (value === true || value === 'next') {
138
+ return '✅';
139
+ } else if (value === false || value === 'experimental') {
140
+ return '❌';
141
+ } else if (value === 'profile') {
142
+ return '📊';
143
+ } else if (value === 'dev') {
144
+ return '💻';
145
+ } else if (typeof value === 'number') {
146
+ return value;
147
+ } else {
148
+ throw new Error(`Unexpected OSS Stable value ${value} for flag ${flag}`);
149
+ }
150
+}
151
+
152
+function getOSSCanaryFlagValue(flag) {
153
+ const value = ReactFeatureFlags[flag];
154
+ if (value === true) {
155
+ return '✅';
156
+ } else if (value === false || value === 'experimental' || value === 'next') {
157
+ return '❌';
158
+ } else if (value === 'profile') {
159
+ return '📊';
160
+ } else if (value === 'dev') {
161
+ return '💻';
162
+ } else if (typeof value === 'number') {
163
+ return value;
164
+ } else {
165
+ throw new Error(`Unexpected OSS Canary value ${value} for flag ${flag}`);
166
+ }
167
+}
168
+
169
+function getOSSExperimentalFlagValue(flag) {
170
+ const value = ReactFeatureFlags[flag];
171
+ if (value === true || value === 'experimental') {
172
+ return '✅';
173
+ } else if (value === false || value === 'next') {
174
+ return '❌';
175
+ } else if (value === 'profile') {
176
+ return '📊';
177
+ } else if (value === 'dev') {
178
+ return '💻';
179
+ } else if (typeof value === 'number') {
180
+ return value;
181
+ } else {
182
+ throw new Error(
183
+ `Unexpected OSS Experimental value ${value} for flag ${flag}`
184
+ );
185
+ }
186
+}
187
+
188
+function getWWWModernFlagValue(flag) {
189
+ const value = ReactFeatureFlagsWWW[flag];
190
+ if (value === true || value === 'experimental') {
191
+ return '✅';
192
+ } else if (value === false || value === 'next') {
193
+ return '❌';
194
+ } else if (value === 'profile') {
195
+ return '📊';
196
+ } else if (value === 'dev') {
197
+ return '💻';
198
+ } else if (value === 'gk') {
199
+ return '🧪';
200
+ } else if (typeof value === 'number') {
201
+ return value;
202
+ } else {
203
+ throw new Error(`Unexpected WWW Modern value ${value} for flag ${flag}`);
204
+ }
205
+}
206
+
207
+function getWWWClassicFlagValue(flag) {
208
+ const value = ReactFeatureFlagsWWW[flag];
209
+ if (value === true) {
210
+ return '✅';
211
+ } else if (value === false || value === 'experimental' || value === 'next') {
212
+ return '❌';
213
+ } else if (value === 'profile') {
214
+ return '📊';
215
+ } else if (value === 'dev') {
216
+ return '💻';
217
+ } else if (value === 'gk') {
218
+ return '🧪';
219
+ } else if (typeof value === 'number') {
220
+ return value;
221
+ } else {
222
+ throw new Error(`Unexpected WWW Classic value ${value} for flag ${flag}`);
223
+ }
224
+}
225
+
226
+function getRNOSSFlagValue(flag) {
227
+ const value = ReactFeatureFlagsNativeOSS[flag];
228
+ if (value === true) {
229
+ return '✅';
230
+ } else if (value === false || value === 'experimental' || value === 'next') {
231
+ return '❌';
232
+ } else if (value === 'profile') {
233
+ return '📊';
234
+ } else if (value === 'dev') {
235
+ return '💻';
236
+ } else if (value === 'gk') {
237
+ return '🧪';
238
+ } else if (typeof value === 'number') {
239
+ return value;
240
+ } else {
241
+ throw new Error(`Unexpected RN OSS value ${value} for flag ${flag}`);
242
+ }
243
+}
244
+
245
+function getRNFBFlagValue(flag) {
246
+ const value = ReactFeatureFlagsNativeFB[flag];
247
+ if (value === true) {
248
+ return '✅';
249
+ } else if (value === false || value === 'experimental' || value === 'next') {
250
+ return '❌';
251
+ } else if (value === 'profile') {
252
+ return '📊';
253
+ } else if (value === 'dev') {
254
+ return '💻';
255
+ } else if (value === 'gk') {
256
+ return '🧪';
257
+ } else if (typeof value === 'number') {
258
+ return value;
259
+ } else {
260
+ throw new Error(`Unexpected RN FB value ${value} for flag ${flag}`);
261
+ }
262
+}
263
+
264
+function argToHeader(arg) {
265
+ switch (arg) {
266
+ case 'www':
267
+ return 'WWW Classic';
268
+ case 'www-modern':
269
+ return 'WWW Modern';
270
+ case 'rn':
271
+ return 'RN OSS';
272
+ case 'rn-fb':
273
+ return 'RN FB';
274
+ case 'canary':
275
+ return 'OSS Canary';
276
+ case 'next':
277
+ return 'OSS Next Major';
278
+ case 'experimental':
279
+ return 'OSS Experimental';
280
+ default:
281
+ return arg;
282
+ }
283
+}
284
+
285
+// Build the table with the value for each flag.
286
+const isDiff = argv.diff != null && argv.diff.length > 1;
287
+const table = {};
288
+// eslint-disable-next-line no-for-of-loops/no-for-of-loops
289
+for (const flag of allFlagsUniqueFlags) {
290
+ const values = {
291
+ 'OSS Next Major': getNextMajorFlagValue(flag),
292
+ 'OSS Canary': getOSSCanaryFlagValue(flag),
293
+ 'OSS Experimental': getOSSExperimentalFlagValue(flag),
294
+ 'WWW Classic': getWWWClassicFlagValue(flag),
295
+ 'WWW Modern': getWWWModernFlagValue(flag),
296
+ 'RN FB': getRNFBFlagValue(flag),
297
+ 'RN OSS': getRNOSSFlagValue(flag),
298
+ };
299
+
300
+ if (!isDiff) {
301
+ table[flag] = values;
302
+ continue;
303
+ }
304
+
305
+ const subset = argv.diff.map(argToHeader).reduce((acc, key) => {
306
+ if (key in values) {
307
+ acc[key] = values[key];
308
+ }
309
+ return acc;
310
+ }, {});
311
+
312
+ if (new Set(Object.values(subset)).size !== 1) {
313
+ table[flag] = subset;
314
+ }
315
+}
316
+
317
+// Sort the table
318
+let sorted = table;
319
+if (isDiff || argv.sort) {
320
+ const sortChannel = argToHeader(isDiff ? argv.diff[0] : argv.sort);
321
+ sorted = Object.fromEntries(
322
+ Object.entries(table).sort(([, rowA], [, rowB]) =>
323
+ rowB[sortChannel].toString().localeCompare(rowA[sortChannel])
324
+ )
325
+ );
326
+}
327
+
328
+if (argv.csv) {
329
+ const csvHeader =
330
+ 'Flag name, WWW Classic, RN FB, OSS Canary, OSS Experimental, WWW Modern, RN OSS\n';
331
+ const csvRows = Object.keys(sorted).map(flag => {
332
+ const row = sorted[flag];
333
+ return `${flag}, ${row['WWW Classic']}, ${row['RN FB']}, ${row['OSS Canary']}, ${row['OSS Experimental']}, ${row['WWW Modern']}, ${row['RN OSS']}`;
334
+ });
335
+ fs.writeFile('./flags.csv', csvHeader + csvRows.join('\n'), function (err) {
336
+ if (err) {
337
+ return console.log(err);
338
+ }
339
+ console.log('The file was saved!');
340
+ });
341
+}
342
+
343
+// left align the flag names.
344
+const maxLength = Math.max(...Object.keys(sorted).map(item => item.length));
345
+const padded = {};
346
+Object.keys(sorted).forEach(key => {
347
+ const newKey = key.padEnd(maxLength, ' ');
348
+ padded[newKey] = sorted[key];
349
+});
350
+
351
+// print table with formatting
352
+console.table(padded);