Skip to content

Commit

Permalink
Bulk editor script for adding/removing tags (mdn#22515)
Browse files Browse the repository at this point in the history
* Bulk editor script for adding tags

* Fix support for --path

* Run prettier

* Use const when possible

* Add license info at top of file

* Change message

* Add support for BCD_DIR, bcd tags remove & bcd tags show

* Add support for npm run bcd

* Restructure files

* Remove unneeded "show" command

* Restructure bulk editor for better extensibility

* Fix ESLint reported issues

* Walk file structure instead of attempting to guess it

* Update scripts/bulk-editor/utils.ts

Co-authored-by: Florian Scholz <[email protected]>

* Add JSDoc

* Allow for wildcards at the end of feature IDs

* any => some

* Add newlines to ends of files again

---------

Co-authored-by: Queen Vinyl Da.i'gyu-Kazotetsu <[email protected]>
Co-authored-by: Florian Scholz <[email protected]>
  • Loading branch information
3 people authored Apr 22, 2024
1 parent dd678d2 commit cc46f48
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 26 deletions.
28 changes: 15 additions & 13 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ import { normalizePath, walk } from './utils/index.js';

const dirname = fileURLToPath(new URL('.', import.meta.url));

export const dataFolders = [
'api',
'browsers',
'css',
'html',
'http',
'javascript',
'mathml',
'svg',
'webassembly',
'webdriver',
'webextensions',
];

/**
* Recursively load one or more directories passed as arguments.
* @param dirs The directories to load
Expand Down Expand Up @@ -52,16 +66,4 @@ const load = async (...dirs: string[]): Promise<CompatData> => {
return result as CompatData;
};

export default await load(
'api',
'browsers',
'css',
'html',
'http',
'javascript',
'mathml',
'svg',
'webassembly',
'webdriver',
'webextensions',
);
export default await load(...dataFolders);
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"show-errors": "npm test 1> /dev/null",
"test": "npm run format && npm run lint && npm run unittest",
"traverse": "node --loader=ts-node/esm --no-warnings=ExperimentalWarning scripts/traverse.ts",
"update-browser-releases": "node --loader=ts-node/esm --no-warnings=ExperimentalWarning scripts/update-browser-releases/index.ts"
"update-browser-releases": "node --loader=ts-node/esm --no-warnings=ExperimentalWarning scripts/update-browser-releases/index.ts",
"bcd": "node --loader=ts-node/esm --no-warnings=ExperimentalWarning scripts/bulk-editor/index.ts"
}
}
9 changes: 9 additions & 0 deletions scripts/bulk-editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';

import tags from './tags/index.js';

yargs(hideBin(process.argv)).command([tags]).demandCommand().help().parse();
30 changes: 30 additions & 0 deletions scripts/bulk-editor/tags/add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

import { updateFeatures } from '../utils.js';

const command = {
command: 'add <tag> <bcd-id..>',
desc: 'Add the following tag to the BCD features',
/**
* handler - Action to perform for 'tags add'
* @param argv Parameter list
*/
handler: (argv) => {
updateFeatures(argv['bcd-id'], (json) => {
// If there is no tags entry, create one
if (!json['tags']) {
json['tags'] = [];
}

// Add the tag
if (!json['tags'].includes(argv['tag'])) {
json['tags'].push(argv['tag']);
}

return json;
});
},
};

export default command;
16 changes: 16 additions & 0 deletions scripts/bulk-editor/tags/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

import addCommand from './add.js';
import removeCommand from './remove.js';

const command = {
command: 'tags',
description: 'Modify tags',
// eslint-disable-next-line @typescript-eslint/no-empty-function
handler: () => {},
builder: (yargs) =>
yargs.command([addCommand, removeCommand]).demandCommand().help(),
};

export default command;
34 changes: 34 additions & 0 deletions scripts/bulk-editor/tags/remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

import { updateFeatures } from '../utils.js';

const command = {
command: 'remove <tag> <bcd-id..>',
desc: 'Remove the following tag from the BCD features',
/**
* handler - Action to perform for 'tags remove'
* @param argv Parameter list
*/
handler: (argv) => {
updateFeatures(argv['bcd-id'], (json) => {
// If there is no tags entry, create one
if (json['tags']) {
const index = json['tags'].indexOf(argv['tag']);
// Actually remove it if found
if (index !== -1) {
json['tags'].splice(index, 1);
}

// Remove the tags array if empty
if (json['tags'].length === 0) {
delete json.tags;
}
}

return json;
});
},
};

export default command;
86 changes: 86 additions & 0 deletions scripts/bulk-editor/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/* This file is a part of @mdn/browser-compat-data
* See LICENSE file for more information. */

import path from 'node:path';
import { fileURLToPath } from 'node:url';
import fs from 'node:fs';

import chalk from 'chalk-template';
import { fdir } from 'fdir';

import { dataFolders } from '../../index.js';
import walk from '../../utils/walk.js';
import stringifyAndOrderProperties from '../lib/stringify-and-order-properties.js';

const dirname = fileURLToPath(new URL('.', import.meta.url));

/**
* Updates the specified key in the given JSON object using the provided updater function.
*
* @param key The key to update in dot notation (e.g., 'api.foobar').
* @param json The JSON object to update.
* @param updater The function to apply to the '__compat' property of the value corresponding to the key.
* @returns The updated JSON object.
*/
const performUpdate = (key, json, updater) => {
const parts = key.split('.');
if (!(parts[0] in json)) {
console.warn('Key not found in file!');
return json;
}
if (parts.length === 1) {
json[parts[0]]['__compat'] = updater(json[parts[0]]['__compat']);
} else {
json[parts[0]] = performUpdate(
parts.slice(1).join('.'),
json[parts[0]],
updater,
);
}
return json;
};

/**
* Updates features in multiple JSON files based on the provided feature IDs and updater function.
*
* @param {string[]} featureIDs An array of feature IDs to update.
* @param {Function} updater The updater function to apply to each matching feature.
*/
export const updateFeatures = (featureIDs, updater) => {
for (const dir of dataFolders) {
const paths = new fdir()
.withBasePath()
.filter((fp) => fp.endsWith('.json'))
.crawl(path.join(dirname, '..', '..', dir))
.sync() as string[];

for (const fp of paths) {
const rawcontents = fs.readFileSync(fp);
const contents = JSON.parse(rawcontents.toString('utf8'));
let changed = false;

const walker = walk(undefined, contents);
for (const { path: featureID } of walker) {
if (
featureIDs.some(
(fid) =>
fid === featureID ||
(fid.endsWith('*') && featureID.startsWith(fid.slice(0, -1))),
)
) {
console.log(chalk`{yellow Updating ${featureID}...}`);
performUpdate(featureID, contents, updater);
changed = true;
}
}

if (changed) {
fs.writeFileSync(
fp,
stringifyAndOrderProperties(contents) + '\n',
'utf-8',
);
}
}
}
};
14 changes: 2 additions & 12 deletions scripts/traverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { hideBin } from 'yargs/helpers';

import { BrowserName, Identifier } from '../types/types.js';
import { InternalSupportStatement } from '../types/index.js';
import bcd from '../index.js';
import bcd, { dataFolders } from '../index.js';

/**
* Traverse all of the features within a specified object and find all features that have one of the specified values
Expand Down Expand Up @@ -135,17 +135,7 @@ const traverseFeatures = (
* @returns The list of features
*/
const main = (
folders = [
'api',
'css',
'html',
'http',
'svg',
'javascript',
'mathml',
'webassembly',
'webdriver',
],
folders = dataFolders.concat('webextensions'),
browsers: BrowserName[] = Object.keys(bcd.browsers) as BrowserName[],
values = ['null', 'true'],
depth = 100,
Expand Down

0 comments on commit cc46f48

Please sign in to comment.