Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 6 additions & 13 deletions bin/create-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@

import { ConfigGenerator } from "../lib/config-generator.js";
import { findPackageJson } from "../lib/utils/npm-utils.js";
import * as log from "../lib/utils/logging.js";
import { intro, outro } from "@clack/prompts";
import process from "node:process";
import fs from "node:fs/promises";
import { parseArgs } from "node:util";

const pkg = JSON.parse(await fs.readFile(new URL("../package.json", import.meta.url), "utf8"));

log.log(`${pkg.name}: v${pkg.version}`);

let options;

try {
Expand All @@ -33,20 +31,13 @@ try {

options = values;
} catch (error) {
log.error(error.message);
// eslint-disable-next-line no-console -- show an error
console.error("Error:", error.message);
// eslint-disable-next-line n/no-process-exit -- exit gracefully on invalid arguments
process.exit(1);
}

process.on("uncaughtException", error => {
if (error instanceof Error && error.code === "ERR_USE_AFTER_CLOSE") {
log.error("Operation canceled");
// eslint-disable-next-line n/no-process-exit -- exit gracefully on Ctrl+C
process.exit(1);
} else {
throw error;
}
});
intro(`${pkg.name}: v${pkg.version}`);

const cwd = process.cwd();
const packageJsonPath = findPackageJson(cwd);
Expand All @@ -72,3 +63,5 @@ if (!options.config) {
await generator.calc();
await generator.output();
}

outro("Thank you");
45 changes: 29 additions & 16 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import process from "node:process";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { writeFile } from "node:fs/promises";
import enquirer from "enquirer";
import * as p from "@clack/prompts";
import semverGreaterThanRange from "semver/ranges/gtr.js";
import semverLessThan from "semver/functions/lt.js";
import { isPackageTypeModule, installSyncSaveDev, fetchPeerDependencies, findPackageJson, parsePackageName } from "./utils/npm-utils.js";
import { getShorthandName } from "./utils/naming.js";
import * as log from "./utils/logging.js";
import { langQuestions, jsQuestions, mdQuestions, installationQuestions, addJitiQuestion } from "./questions.js";

//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -86,6 +85,21 @@ function addEnvironmentConfig({ env, devDependencies }) {
};
}

/**
* `p.group` wrapper.
* @param {Record<string, () => Promise<any>>} prompts Prompts Object.
* @returns {Promise<Record<string, any>>} Prompts result.
*/
function groupPrompts(prompts) {
return p.group(prompts, {
onCancel() {
p.cancel("Operation cancelled.");
// eslint-disable-next-line n/no-process-exit -- Exit gracefully
process.exit(0);
}
});
}

//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -119,23 +133,23 @@ export class ConfigGenerator {
* @returns {Promise<void>}
*/
async prompt() {
Object.assign(this.answers, await enquirer.prompt(langQuestions));
Object.assign(this.answers, await groupPrompts(langQuestions));

if (this.answers.languages.includes("javascript")) {
Object.assign(this.answers, await enquirer.prompt(jsQuestions));
Object.assign(this.answers, await groupPrompts(jsQuestions));
}

if (this.answers.languages.includes("md")) {
Object.assign(this.answers, await enquirer.prompt(mdQuestions));
Object.assign(this.answers, await groupPrompts(mdQuestions));
}

if (this.answers.configFileLanguage === "ts") {
const nodeVersion = process.versions.node;

// Node.js v24.3.0 removed the experimental warning from type stripping.
if (semverLessThan(nodeVersion, "24.3.0")) {
log.info("Jiti is required for Node.js <24.3.0 to read TypeScript configuration files.");
Object.assign(this.answers, await enquirer.prompt(addJitiQuestion));
p.log.info("Jiti is required for Node.js <24.3.0 to read TypeScript configuration files.");
Object.assign(this.answers, await groupPrompts(addJitiQuestion));
}
}
}
Expand Down Expand Up @@ -353,15 +367,14 @@ export default defineConfig([\n${exportContent || " {}\n"}]);\n`; // defaults t
*/
async output() {

log.info("The config that you've selected requires the following dependencies:\n");
log.log(this.result.devDependencies.join(", "));

p.log.info("The config that you've selected requires the following dependencies:");
p.note(this.result.devDependencies.join(", "), "Required Dependencies");
Comment thread
hyperz111 marked this conversation as resolved.

const { executeInstallation, packageManager } = (await enquirer.prompt(installationQuestions));
const { executeInstallation, packageManager } = (await groupPrompts(installationQuestions));
const configPath = path.join(this.cwd, this.result.configFilename);

if (executeInstallation === true) {
log.log("☕️Installing...");
p.log.step("☕️Installing...");
installSyncSaveDev(this.result.devDependencies, packageManager, this.result.installFlags);
await writeFile(configPath, this.result.configContent);

Expand All @@ -371,15 +384,15 @@ export default defineConfig([\n${exportContent || " {}\n"}]);\n`; // defaults t
const result = spawnSync(process.execPath, [eslintBin, "--fix", "--quiet", configPath], { encoding: "utf8" });

if (result.error || result.status !== 0) {
log.error("A config file was generated, but the config file itself may not follow your linting rules.");
p.log.error("A config file was generated, but the config file itself may not follow your linting rules.");
} else {
log.success(`Successfully created ${configPath} file.`);
p.log.success(`Successfully created ${configPath} file.`);
}
} else {
await writeFile(configPath, this.result.configContent);

log.success(`Successfully created ${configPath} file.`);
log.warn("You will need to install the dependencies yourself.");
p.log.success(`Successfully created ${configPath} file.`);
p.log.warn("You will need to install the dependencies yourself.");
}
}
}
223 changes: 95 additions & 128 deletions lib/questions.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,152 +11,119 @@
// Imports
// ------------------------------------------------------------------------------

import colors from "ansi-colors";

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
* Set questions prompt style options in here.
* @param {PlainObject[]} questionsPromptArray Array of questions prompt.
* @returns {PlainObject[]} Questions prompt with style options.
*/
function setQuestionsPromptStyle(questionsPromptArray) {
return questionsPromptArray.map(opts => ({
...opts,
symbols: {

// For option symbol in select and multiselect
indicator: {
on: colors.cyan(colors.symbols.radioOn),
off: colors.gray(colors.symbols.radioOff)
}
}
}));
}
import * as p from "@clack/prompts";

// ------------------------------------------------------------------------------
// Exports
// ------------------------------------------------------------------------------

export const langQuestions = setQuestionsPromptStyle([{
type: "multiselect",
name: "languages",
message: "What do you want to lint?",
choices: [
{ message: "JavaScript", name: "javascript" },
{ message: "JSON", name: "json" },
{ message: "JSON with comments", name: "jsonc" },
{ message: "JSON5", name: "json5" },
{ message: "Markdown", name: "md" },
{ message: "CSS", name: "css" }
],
initial: 0
}, {
type: "select",
name: "purpose",
message: "How would you like to use ESLint?",
initial: 1,
choices: [
{ message: "To check syntax only", name: "syntax" },
{ message: "To check syntax and find problems", name: "problems" }
]
}]);
export const langQuestions = {
languages: () => p.multiselect({
message: "What do you want to lint?",
options: [
{ label: "JavaScript", value: "javascript" },
{ label: "JSON", value: "json" },
{ label: "JSON with comments", value: "jsonc" },
{ label: "JSON5", value: "json5" },
{ label: "Markdown", value: "md" },
{ label: "CSS", value: "css" }
],
initialValues: ["javascript"]
}),
purpose: () => p.select({
message: "How would you like to use ESLint?",
initialValue: "problems",
options: [
{ label: "To check syntax only", value: "syntax" },
{ label: "To check syntax and find problems", value: "problems" }
]
})
};

export const jsQuestions = setQuestionsPromptStyle([
{
type: "select",
name: "moduleType",
export const jsQuestions = {
moduleType: () => p.select({
message: "What type of modules does your project use?",
initial: 0,
choices: [
{ message: "JavaScript modules (import/export)", name: "esm" },
{ message: "CommonJS (require/exports)", name: "commonjs" },
{ message: "None of these", name: "script" }
initialValue: "esm",
options: [
{ label: "JavaScript modules (import/export)", value: "esm" },
{ label: "CommonJS (require/exports)", value: "commonjs" },
{ label: "None of these", value: "script" }
]
},
{
type: "select",
name: "framework",
}),
framework: () => p.select({
message: "Which framework does your project use?",
initial: 0,
choices: [
{ message: "React", name: "react" },
{ message: "Vue.js", name: "vue" },
{ message: "None of these", name: "none" }
initialValue: "react",
options: [
{ label: "React", value: "react" },
{ label: "Vue.js", value: "vue" },
{ label: "None of these", value: "none" }
]
},
{
type: "toggle",
name: "useTs",
}),
useTs: () => p.confirm({
message: "Does your project use TypeScript?",
disabled: "No",
enabled: "Yes",
initial: 0
},
{
type: "multiselect",
name: "env",
initialValue: false
}),
env: () => p.multiselect({
message: "Where does your code run?",
hint: "(Press <space> to select, <a> to toggle all, <i> to invert selection)",
initial: 0,
choices: [
{ message: "Browser", name: "browser" },
{ message: "Node", name: "node" }
initialValues: ["browser"],
options: [
{ label: "Browser", value: "browser" },
{ label: "Node", value: "node" }
]
},
{
type: "select",
name: "configFileLanguage",
message: "Which language do you want your configuration file be written in?",
initial: 0,
choices: [
{ message: "JavaScript", name: "js" },
{ message: "TypeScript", name: "ts" }
],
skip() {
return !this.state.answers.useTs;
}),
async configFileLanguage({ results }) {
if (results.useTs) {
return await p.select({
message: "Which language do you want your configuration file be written in?",
initialValue: "js",
options: [
{ label: "JavaScript", value: "js" },
{ label: "TypeScript", value: "ts" }
]
});
}

return "js";
}
]);
};

export const mdQuestions = setQuestionsPromptStyle([{
type: "select",
name: "mdType",
message: "What flavor of Markdown do you want to lint?",
initial: 0,
choices: [
{ message: "CommonMark", name: "commonmark" },
{ message: "GitHub Flavored Markdown", name: "gfm" }
]
}]);
export const mdQuestions = {
mdType: () => p.select({
message: "What flavor of Markdown do you want to lint?",
initialValue: "commonmark",
options: [
{ label: "CommonMark", value: "commonmark" },
{ label: "GitHub Flavored Markdown", value: "gfm" }
]
})
};

export const installationQuestions = setQuestionsPromptStyle([
{
type: "toggle",
name: "executeInstallation",
export const installationQuestions = {
executeInstallation: () => p.confirm({
message: "Would you like to install them now?",
enabled: "Yes",
disabled: "No",
initial: 1
}, {
type: "select",
name: "packageManager",
message: "Which package manager do you want to use?",
initial: 0,
choices: ["npm", "yarn", "pnpm", "bun"],
skip() {
return this.state.answers.executeInstallation === false;
initialValue: true
}),
async packageManager({ results }) {
if (results.executeInstallation) {
return await p.select({
message: "Which package manager do you want to use?",
initialValue: "npm",
options: [
{ value: "npm" },
{ value: "yarn" },
{ value: "pnpm" },
{ value: "bun" }
Comment thread
hyperz111 marked this conversation as resolved.
]
});
}

return "npm";
}
]);
};

export const addJitiQuestion = setQuestionsPromptStyle([{
type: "toggle",
name: "addJiti",
message: "Would you like to add Jiti as a devDependency?",
disabled: "No",
enabled: "Yes",
initial: 1
}]);
export const addJitiQuestion = {
addJiti: () => p.confirm({
message: "Would you like to add Jiti as a devDependency?",
initialValue: true
})
};
Loading