UNPKG

lint-to-the-future

Version:

A modern way to progressively update your code to the best practices

253 lines (208 loc) 8.48 kB
#!/usr/bin/env node /* eslint-disable prettier/prettier */ const { join } = require('path'); const { writeFileSync, readFileSync } = require('fs'); const { copy, mkdirp } = require('fs-extra'); const importCwd = require('import-cwd'); const fetch = require('node-fetch'); const { Command } = require('commander'); const { inspect } = require('util'); const program = new Command(); async function list(lttfPlugins, previousResultsPath) { let pluginResults = {}; for (let plugin of lttfPlugins) { pluginResults[plugin.name] = await plugin.import.list(); } let previousResults = {} if (previousResultsPath) { if (previousResultsPath.match(/((http(s?)):\/\/)/)) { const response = await fetch(previousResultsPath); if(response.ok) { previousResults = await response.json(); } else { console.warn(`Error ${response.status} when downloading previous results. Previous results ignored`); } } else { try { let file = readFileSync(previousResultsPath); previousResults = JSON.parse(file); } catch { console.warn(`Error ${previousResultsPath} could not be found. Previous results ignored`); } } } // Squash old entries for (let date in previousResults) { for (let plugin in previousResults[date]) { for (let rule in previousResults[date][plugin]) { if (Array.isArray(previousResults[date][plugin][rule])) { previousResults[date][plugin][rule] = previousResults[date][plugin][rule].length; } } } } let today = new Date(); let isoDate = today.toISOString().split('T')[0]; previousResults[isoDate] = pluginResults; return previousResults; } async function output({ lttfPlugins, outputPath, rootUrl, previousResultsPath, outputFolders }) { if (!output) { console.error('You must provide an output directory to `output` with -o'); } const ouputResult = await list(lttfPlugins, previousResultsPath); await copy(join(__dirname, 'dist'), outputPath); if(rootUrl) { let index = readFileSync(join(outputPath, 'index.html'), 'utf8'); let regex = /<meta name="lint-to-the-future\/config\/environment" content="(.*)" \/>/; let envContentString = index.match(regex)[1]; let envContent = JSON.parse(decodeURIComponent(envContentString)); envContent.rootURL = `/${rootUrl}/`; writeFileSync( join(outputPath, 'index.html'), index .replace(regex, `<meta name="lint-to-the-future/config/environment" content="${encodeURIComponent(JSON.stringify(envContent))}" />`) .replace(/"\/assets\//g, `"/${rootUrl}/assets/`), ); } if (outputFolders) { let index = readFileSync(join(outputPath, 'index.html'), 'utf8'); const ruleNames = new Set(); for( let [, plugins] of Object.entries(ouputResult)) { for(let plugin in plugins ) { for (let rule in plugins[plugin]) { ruleNames.add(`${plugin}:${rule}`) } } } for (let ruleName of ruleNames) { let ruleFolder = join(outputPath, 'rule', ruleName); await mkdirp(ruleFolder); writeFileSync(join(ruleFolder, 'index.html'), index) } } writeFileSync(join(outputPath, 'data.json'), JSON.stringify(ouputResult)); } async function getLttfPlugins() { let currentPackageJSON = require(join(process.cwd(), 'package.json')); let lttfPluginsNames = [ ...Object.keys(currentPackageJSON.devDependencies || {}), ...Object.keys(currentPackageJSON.dependencies || {}) ].filter(dep => dep.startsWith('lint-to-the-future-')); let lttfPlugins = []; for (const name of lttfPluginsNames) { let mod; try { mod = await import(join(process.cwd(), 'node_modules', name, 'main.mjs')); } catch (err) { // fallback to trying importCwd mod = importCwd(name); } lttfPlugins.push({ import: mod, name, }) } return lttfPlugins; } function getFirstObjectFromObjectKeys(object) { let key = Object.keys(object)[0]; return object[key]; } program .name('lint-to-the-future') .description('A modern way to progressively update your code to the best practices using lint rules') .version(require(join(__dirname, 'package.json')).version); program .command('output') .description('Generates a dashboard webpage to help track which files have file-based ignore directives in them') .requiredOption('-o, --output <path>', 'Output path for dashboard webpage') .option('--rootUrl <path|url>', 'Required if the dashboard is not hosted on your servers rootUrl') .option('--previous-results <path|url>', 'This should be a path or URL to the previous data.json file that was generated when this command was last run') .option('--output-folders', 'Creates a folder structure for each rule page') .action(async ({output: outputPath, rootUrl, previousResults, outputFolders }) => { let lttfPlugins = await getLttfPlugins(); output({ lttfPlugins, outputPath, rootUrl, previousResultsPath: previousResults, outputFolders }); }) program .command('list') .description('prints the current files with file-based lint ignore directives') .option('-o, --output <path>', 'output path for lttf list') .option('--stdout', 'print lttf list to console') .option('--previous-results <path|url>', 'This should be a path or URL to the previous data.json file that was generated when this command was last run') .action(async ({ output: outputPath, stdout, previousResults }) => { if (!outputPath && !stdout) { console.error('You must provide an output path to `list` with -o or pass --stdout'); } let lttfPlugins = await getLttfPlugins(); const listResult = await list(lttfPlugins, previousResults); if (stdout) { console.log(inspect(listResult, { depth: 5 })); } if (outputPath) { writeFileSync(outputPath, JSON.stringify(listResult)); } }); program .command('ignore') .description('Add file-based ignores to any file that is currently erroring') .option('-p, --plugin <string>', 'only run ignore on one plugin') .option('-f, --filter <string>', 'only apply to the filtered files') .action(async (options) => { function executePlugin(plugin) { // this makes sure that even when if (plugin.import.capabilities?.includes('filter-ignore')) { return plugin.import.ignoreAll({filter: options.filter}); } else { if (options.filter) { program.error(`Plugin ${plugin.name} does not support passing '--filter' to the ignore command. Please update or contact the plugin developers`); return; } plugin.import.ignoreAll() } } let lttfPlugins = await getLttfPlugins(); if(options.plugin) { let plugin = lttfPlugins.find(p => p.name === options.plugin) if(!plugin) { program.error(`Could not find plugin with specified name ${options.plugin}. Available plugins are: ${lttfPlugins.map(p => p.name).join(', ')}`) return; } await executePlugin(plugin); } else { for (let plugin of lttfPlugins) { await executePlugin(plugin); } } }); program .command('remove') .description('Remove file-based ignores in every file for a specified rule') .argument('<rule-name>', 'Name of lint rule to remove file-based ignores') .option('-f, --filter <string>', 'only apply to the filtered files') .action(async(lintRuleName, options) => { let lttfPlugins = await getLttfPlugins(); let listResult = getFirstObjectFromObjectKeys(await list(lttfPlugins)); let pluginsWithRuleName = []; for (const pluginKey in listResult) { if (listResult[pluginKey][lintRuleName]) { let plugin = lttfPlugins.find(plugin => plugin.name === pluginKey); pluginsWithRuleName.push(plugin); } } if (!pluginsWithRuleName.length) { console.error(`No file-based ignores could be found for the lint rule '${lintRuleName}'`); return; } async function removeIgnore(plugin, lintRuleName) { if (plugin.import.remove) { await plugin.import.remove({name: lintRuleName, filter: options.filter}); } else { console.error(`The 'remove' command is not supported by the plugin ${plugin.name}. Please update or contact the plugin developers`) } } for (let plugin of pluginsWithRuleName) { await removeIgnore(plugin, lintRuleName); } }); program.parse();