-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0431698
commit 48c82d6
Showing
4 changed files
with
294 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,14 @@ | ||
#!/usr/bin/env node | ||
|
||
/** | ||
* @fileoverview Main CLI that is run via the eslint command. | ||
* @author Nicholas C. Zakas | ||
* @fileoverview Main CLI that is run via the `npm init @eslint/config` command. | ||
* @author 唯然<[email protected]> | ||
*/ | ||
|
||
/* eslint no-console:off -- CLI */ | ||
import { initializeConfig } from "../lib/init/config-initializer.js"; | ||
initializeConfig(); | ||
import { ConfigGenerator } from "../lib/config-generator.js"; | ||
|
||
const generator = new ConfigGenerator(); | ||
|
||
generator.prompt(); | ||
generator.calc(); | ||
generator.output(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/** | ||
* @fileoverview to generate config files. | ||
* @author 唯然<[email protected]> | ||
*/ | ||
import process from "process"; | ||
import path from "path"; | ||
import { writeFile } from "fs/promises"; | ||
import { isPackageTypeModule } from "./utils/npm-utils.js"; | ||
|
||
/** | ||
* | ||
*/ | ||
export class ConfigGenerator { | ||
constructor(options) { | ||
this.cwd = options.cwd || process.cwd(); | ||
this.answers = options.answers || {}; | ||
this.result = { | ||
devDependencies: [], | ||
configPath: "", | ||
configContent: "" | ||
}; | ||
} | ||
|
||
prompt() { | ||
|
||
// TODO: ask users to input | ||
this.answers = { | ||
purpose: "syntax", | ||
module: "esm", | ||
framework: "vue", | ||
typescript: true | ||
}; | ||
} | ||
|
||
calc() { | ||
const isModule = isPackageTypeModule(); | ||
|
||
this.result.configPath = path.join(this.cwd, isModule ? "eslint.config.js" : "eslint.config.mjs"); | ||
|
||
let importContent = ""; | ||
let exportContent = ""; | ||
|
||
// TODO: we may need to use "@eslint/eslintrc" as these plugins have not support flat configs yet? | ||
if (this.answers.typescript) { | ||
this.result.devDependencies.push("@typescript-eslint/eslint-plugin"); | ||
importContent += "import PluginTs from '@typescript-eslint/eslint-plugin';\n"; | ||
exportContent += "PluginTs.configs.recommended,"; | ||
} | ||
|
||
if (this.answers.framework === "vue") { | ||
this.result.devDependencies.push("eslint-plugin-vue"); | ||
importContent += "import PluginVue from 'eslint-plugin-vue';\n"; | ||
exportContent += "PluginVue.configs.recommended,"; | ||
} | ||
|
||
if (this.answers.framework === "react") { | ||
this.result.devDependencies.push("eslint-plugin-react"); | ||
importContent += "import PluginReact from 'eslint-plugin-react';\n"; | ||
exportContent += "PluginReact.configs.recommended,"; | ||
} | ||
|
||
this.result.configContent = `${importContent}export default [${exportContent}];`; | ||
} | ||
|
||
async output() { | ||
await writeFile(this.result.configPath, this.result.configContent); | ||
|
||
// TODO: install this.result.devDependencies | ||
// TODO: is peerDependencies still needed? | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/** | ||
* @fileoverview Handle logging for ESLint | ||
* @author Gyandeep Singh | ||
*/ | ||
|
||
|
||
/* eslint no-console: "off" -- Logging util */ | ||
|
||
/** | ||
* Cover for console.log | ||
* @param {...any} args The elements to log. | ||
* @returns {void} | ||
*/ | ||
export function info(...args) { | ||
console.log(...args); | ||
} | ||
|
||
/** | ||
* Cover for console.error | ||
* @param {...any} args The elements to log. | ||
* @returns {void} | ||
*/ | ||
export function error(...args) { | ||
console.error(...args); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/** | ||
* @fileoverview Utility for executing npm commands. | ||
* @author Ian VanSchooten | ||
*/ | ||
|
||
|
||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
|
||
import fs from "fs"; | ||
import spawn from "cross-spawn"; | ||
|
||
import path from "path"; | ||
import * as log from "./logging.js"; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Helpers | ||
//------------------------------------------------------------------------------ | ||
|
||
/** | ||
* Find the closest package.json file, starting at process.cwd (by default), | ||
* and working up to root. | ||
* @param {string} [startDir=process.cwd()] Starting directory | ||
* @returns {string} Absolute path to closest package.json file | ||
*/ | ||
function findPackageJson(startDir) { | ||
let dir = path.resolve(startDir || process.cwd()); | ||
|
||
do { | ||
const pkgFile = path.join(dir, "package.json"); | ||
|
||
if (!fs.existsSync(pkgFile) || !fs.statSync(pkgFile).isFile()) { | ||
dir = path.join(dir, ".."); | ||
continue; | ||
} | ||
return pkgFile; | ||
} while (dir !== path.resolve(dir, "..")); | ||
return null; | ||
} | ||
|
||
//------------------------------------------------------------------------------ | ||
// Private | ||
//------------------------------------------------------------------------------ | ||
|
||
/** | ||
* Install node modules synchronously and save to devDependencies in package.json | ||
* @param {string|string[]} packages Node module or modules to install | ||
* @param {string} packageManager Package manager to use for installation. | ||
* @returns {void} | ||
*/ | ||
function installSyncSaveDev(packages, packageManager = "npm") { | ||
const packageList = Array.isArray(packages) ? packages : [packages]; | ||
const installCmd = packageManager === "yarn" ? "add" : "install"; | ||
const installProcess = spawn.sync(packageManager, [installCmd, "-D"].concat(packageList), { stdio: "inherit" }); | ||
const error = installProcess.error; | ||
|
||
if (error && error.code === "ENOENT") { | ||
const pluralS = packageList.length > 1 ? "s" : ""; | ||
|
||
log.error(`Could not execute ${packageManager}. Please install the following package${pluralS} with a package manager of your choice: ${packageList.join(", ")}`); | ||
} | ||
} | ||
|
||
/** | ||
* Fetch `peerDependencies` of the given package by `npm show` command. | ||
* @param {string} packageName The package name to fetch peerDependencies. | ||
* @returns {Object} Gotten peerDependencies. Returns null if npm was not found. | ||
*/ | ||
function fetchPeerDependencies(packageName) { | ||
const npmProcess = spawn.sync( | ||
"npm", | ||
["show", "--json", packageName, "peerDependencies"], | ||
{ encoding: "utf8" } | ||
); | ||
|
||
const error = npmProcess.error; | ||
|
||
if (error && error.code === "ENOENT") { | ||
return null; | ||
} | ||
const fetchedText = npmProcess.stdout.trim(); | ||
|
||
return JSON.parse(fetchedText || "{}"); | ||
|
||
|
||
} | ||
|
||
/** | ||
* Check whether node modules are include in a project's package.json. | ||
* @param {string[]} packages Array of node module names | ||
* @param {Object} opt Options Object | ||
* @param {boolean} opt.dependencies Set to true to check for direct dependencies | ||
* @param {boolean} opt.devDependencies Set to true to check for development dependencies | ||
* @param {boolean} opt.startdir Directory to begin searching from | ||
* @throws {Error} If cannot find valid `package.json` file. | ||
* @returns {Object} An object whose keys are the module names | ||
* and values are booleans indicating installation. | ||
*/ | ||
function check(packages, opt) { | ||
const deps = new Set(); | ||
const pkgJson = (opt) ? findPackageJson(opt.startDir) : findPackageJson(); | ||
|
||
if (!pkgJson) { | ||
throw new Error("Could not find a package.json file. Run 'npm init' to create one."); | ||
} | ||
|
||
const fileJson = JSON.parse(fs.readFileSync(pkgJson, "utf8")); | ||
|
||
["dependencies", "devDependencies"].forEach(key => { | ||
if (opt[key] && typeof fileJson[key] === "object") { | ||
Object.keys(fileJson[key]).forEach(dep => deps.add(dep)); | ||
} | ||
}); | ||
|
||
return packages.reduce((status, pkg) => { | ||
status[pkg] = deps.has(pkg); | ||
return status; | ||
}, {}); | ||
} | ||
|
||
/** | ||
* Check whether node modules are included in the dependencies of a project's | ||
* package.json. | ||
* | ||
* Convenience wrapper around check(). | ||
* @param {string[]} packages Array of node modules to check. | ||
* @param {string} rootDir The directory containing a package.json | ||
* @returns {Object} An object whose keys are the module names | ||
* and values are booleans indicating installation. | ||
*/ | ||
function checkDeps(packages, rootDir) { | ||
return check(packages, { dependencies: true, startDir: rootDir }); | ||
} | ||
|
||
/** | ||
* Check whether node modules are included in the devDependencies of a project's | ||
* package.json. | ||
* | ||
* Convenience wrapper around check(). | ||
* @param {string[]} packages Array of node modules to check. | ||
* @returns {Object} An object whose keys are the module names | ||
* and values are booleans indicating installation. | ||
*/ | ||
function checkDevDeps(packages) { | ||
return check(packages, { devDependencies: true }); | ||
} | ||
|
||
/** | ||
* Check whether package.json is found in current path. | ||
* @param {string} [startDir] Starting directory | ||
* @returns {boolean} Whether a package.json is found in current path. | ||
*/ | ||
function checkPackageJson(startDir) { | ||
return !!findPackageJson(startDir); | ||
} | ||
|
||
/** | ||
* check if the package.type === "module" | ||
* @returns {boolean} return true if the package.type === "module" | ||
*/ | ||
function isPackageTypeModule() { | ||
const pkgJSONPath = findPackageJson(); | ||
|
||
if (pkgJSONPath) { | ||
const pkgJSONContents = JSON.parse(fs.readFileSync(pkgJSONPath, "utf8")); | ||
|
||
if (pkgJSONContents.type === "module") { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
|
||
//------------------------------------------------------------------------------ | ||
// Public Interface | ||
//------------------------------------------------------------------------------ | ||
|
||
export { | ||
installSyncSaveDev, | ||
fetchPeerDependencies, | ||
findPackageJson, | ||
checkDeps, | ||
checkDevDeps, | ||
checkPackageJson, | ||
isPackageTypeModule | ||
}; |