|
/** |
|
* Find and list every hashed class being used in our projects' Custom CSS |
|
* @usage npx ts-node bin/custom-css-search.js |
|
* @option --dataset=[classes|project] |
|
* @note you can pipe the output directly in to a file using `tee`: |
|
* `... | tee custom-css-usage.json >/dev/null` |
|
*/ |
|
|
|
try { |
|
require('dotenv').config(); // eslint-disable-line global-require |
|
} catch (e) {} // eslint-disable-line no-empty |
|
|
|
const fs = require('fs/promises'); |
|
const config = require('config'); |
|
const mongoose = require('@readme/models/mongoose'); |
|
|
|
let Parker; |
|
try { |
|
Parker = require('parker/lib/Parker'); |
|
} catch (e) { |
|
console.log('\n🛑 Error\n You need to install Parker to run this script!\n\n $ npm install parker\n'); |
|
process.exit(0); |
|
} |
|
|
|
require('@readme/models'); |
|
|
|
const Project = mongoose.model('Project'); |
|
|
|
const sortObject = (obj, cb) => |
|
Object.fromEntries( |
|
Object.entries(obj) |
|
.sort((a, b) => cb(a, b)) |
|
.reverse(), |
|
); |
|
|
|
const dataset = process.argv |
|
.find(e => /--dataset=(classes|projects)/i.test(e)) |
|
?.replaceAll('-', '') |
|
.split('=')[1] |
|
.toLowerCase(); |
|
|
|
async function main() { |
|
await mongoose.connect(config.db); |
|
|
|
let classes; |
|
try { |
|
classes = (await fs.readFile(`${__dirname}/search-classes.txt`, 'utf-8').then(c => c.split('\n'))).filter(Boolean); |
|
if (!classes.length) throw new Error('No classes defined!'); |
|
} catch (e) { |
|
console.log( |
|
"\n🛑 Error:\n You need to add a list of class selectors to search for!\n To create one, run:\n\n $ echo 'MatchClass-zed\\nMatchClass-one' | tee bin/search-classes.txt > /dev/null\n", |
|
); |
|
process.exit(0); |
|
} |
|
|
|
const projects = await Project.find({ |
|
is_active: true, |
|
'appearance.stylesheet_hub2': { |
|
$exists: true, |
|
$ne: '', |
|
}, |
|
}); |
|
|
|
const parker = new Parker([ |
|
{ |
|
id: 'classes', |
|
type: 'identifier', |
|
aggregate: 'list', |
|
format: 'list', |
|
measure(selector) { |
|
if (selector.startsWith('.')) { |
|
if (classes.includes(selector.substr(1))) return selector; |
|
} |
|
return false; |
|
}, |
|
filter(value, index, self) { |
|
return self.indexOf(value) === index; |
|
}, |
|
}, |
|
]); |
|
|
|
let matchStats = {}; |
|
let usageStats = {}; |
|
|
|
const promises = projects.map(async project => { |
|
const results = parker.run(project.appearance.stylesheet_hub2); |
|
|
|
if (!results.classes.length) return; |
|
|
|
// eslint-disable-next-line consistent-return |
|
return new Promise(resolve => { |
|
results.classes.forEach(c => { |
|
matchStats[c] = matchStats[c] + 1 || 1; |
|
}); |
|
usageStats[project.subdomain] = { |
|
name: project.name || project.subdomain, |
|
plan: project.planOverride || project.plan, |
|
count: results.classes.length, |
|
classes: results.classes.sort(), |
|
link: project.childrenProjects.length |
|
? `https://dash.readme.com/group/${project.subdomain}/stylesheet` |
|
: `http://${project.subdomain}.readme.io/dash?to=appearance-stylesheet`, |
|
}; |
|
resolve(); |
|
}); |
|
}); |
|
|
|
await Promise.all(promises); |
|
|
|
console.log(); |
|
if (dataset === 'classes') { |
|
matchStats = sortObject(matchStats, ([, a], [, b]) => a - b); |
|
console.log(JSON.stringify(matchStats, null, 2)); |
|
} else if (dataset === 'projects') { |
|
usageStats = sortObject(usageStats, ([, { count: a }], [, { count: b }]) => a - b); |
|
console.log(JSON.stringify(usageStats, null, 2)); |
|
} else { |
|
console.log('🐷 Matched Classes:', Object.keys(matchStats).length); |
|
console.log(' Matched Projects:', Object.keys(usageStats).length); |
|
if (typeof dataset === 'undefined') |
|
console.log('\n (Pass --dataset=[projects|classes] to get more detailed information.)'); |
|
} |
|
console.log(); |
|
|
|
process.exit(0); |
|
} |
|
|
|
main(); |