blob: 7dbf5d8c3567b816aaaa7409c722aaa3b3c699c7 [file] [log] [blame] [edit]
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
import { join, relative, resolve, sep } from 'node:path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { Identifier } from '../types/types';
const argv = yargs(hideBin(process.argv))
.command('$0 [files..]', 'Split BCD JSON files', (yargs) => {
yargs.positional('files', {
describe: 'One or more JSON files to split',
type: 'string',
});
})
.help().argv as any as { files: string[] };
/**
* Extracts the base key path (as array of strings) from a JSON file path.
*
* For example, `api/AbortController.json` becomes `['api', 'AbortController']`.
* @param filePath - The path to the BCD JSON file
* @returns Array of path components without the `.json` extension
*/
const getBaseKeyFromPath = (filePath: string): string[] => {
const relativePath = filePath.replace(/\.json$/, '').split(sep);
return relativePath;
};
/**
* Creates the JSON for the subfeature.
* @param baseKeys - The parent keys in which to nest the data.
* @param key - The key of the subfeature.
* @param value - The value of the subfeature.
* @returns JSON for the subfeature data nested in parent structure.
*/
const createSubfeatureJSON = (
baseKeys: string[],
key: string,
value: Identifier,
) => {
const data = {};
let current = data;
for (const baseKey of baseKeys) {
current[baseKey] = {};
current = current[baseKey];
}
current[key] = value;
return data;
};
/**
* Checks if data contains compat data.
* @param data - The data to check for `__compat` entries.
* @returns TRUE, if the data contains any compat data.
*/
const hasCompatData = (data: any) =>
'__compat' in data ||
(typeof data === 'object' && Object.values(data).some(hasCompatData));
/**
* Writes a JSON file.
* @param path - The path to the file.
* @param data - The data to write as JSON.
* @returns Promise.
*/
const writeJSONFile = async (path: string, data: Identifier) =>
writeFile(path, JSON.stringify(data, null, 2) + '\n');
/**
* Splits a BCD-style JSON file into separate files for each subfeature.
* Each file will include only the `__compat` data of a given property.
* Extracted entries are also removed from the original file.
* @param file - The absolute or relative path to the source JSON file
*/
const splitFile = async (file: string) => {
const fullPath = resolve(file);
const raw = await readFile(fullPath, 'utf-8');
const data = JSON.parse(raw) as Identifier;
const baseKeys = getBaseKeyFromPath(file);
let current = data;
for (const key of baseKeys) {
if (!(key in current)) {
console.error(`❌ Error: Key "${key}" not found in structure.`);
return;
}
current = current[key];
}
const dirPath = fullPath.replace(/\.json$/, '');
// Write file for each subfeature, then remove subfeature from parent file.
for (const [key, value] of Object.entries(current)) {
if (key === '__compat') {
continue;
}
if (typeof value === 'object' && '__compat' in value) {
const data = createSubfeatureJSON(baseKeys, key, value);
await mkdir(dirPath, { recursive: true });
const outFile = join(dirPath, `${key}.json`);
await writeJSONFile(outFile, data);
console.log(`✔ Created: ${relative(process.cwd(), outFile)}`);
Reflect.deleteProperty(current, key);
}
}
// Check if current file still has any data.
if (hasCompatData(data)) {
await writeJSONFile(fullPath, data);
console.log(`✔ Updated: ${file}`);
} else {
await rm(fullPath);
console.log(`✔ Deleted: ${file}`);
}
};
await Promise.all(argv.files.map(splitFile));