@promakeai/eslint 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,200 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { select, confirm } from "@inquirer/prompts";
5
+ import pc from "picocolors";
6
+ import { detectProjectType } from "../utils/detect.js";
7
+ import { detectPackageManager, getInstallCommand, } from "../utils/pm.js";
8
+ import { exec } from "../utils/exec.js";
9
+ import { getEditorSettingsContent, getEslintConfigContent, getPrettierRcContent, getScripts, } from "../utils/templates.js";
10
+ import { log } from "../utils/log.js";
11
+ export async function init(options = {}) {
12
+ const cwd = process.cwd();
13
+ const yes = options.yes ?? false;
14
+ const allowPrettier = options.prettier ?? true;
15
+ console.log();
16
+ console.log(pc.bold("@promakeai/eslint") + " " + pc.dim("- ESLint + Prettier setup"));
17
+ console.log();
18
+ // Step 1: Verify package.json exists
19
+ const pkgPath = resolve(cwd, "package.json");
20
+ if (!existsSync(pkgPath)) {
21
+ log.error("No package.json found. Run this command from a project root.");
22
+ process.exit(1);
23
+ }
24
+ // Step 2: Detect or ask project type
25
+ let projectType;
26
+ if (options.projectType) {
27
+ const allowed = ["react-vite", "react-lib", "node"];
28
+ if (!allowed.includes(options.projectType)) {
29
+ log.error(`Invalid --project value: ${pc.bold(options.projectType)}. ` +
30
+ "Use react-vite, react-lib, or node.");
31
+ process.exit(1);
32
+ }
33
+ projectType = options.projectType;
34
+ log.info(`Project type: ${pc.bold(projectType)} (via --project)`);
35
+ }
36
+ else {
37
+ const detection = await detectProjectType(cwd);
38
+ if (detection.detected) {
39
+ log.info(`Detected: ${pc.bold(detection.detected)} (${detection.confidence})`);
40
+ const useDetected = yes
41
+ ? true
42
+ : await confirm({
43
+ message: `Set up as ${pc.bold(detection.detected)} project?`,
44
+ default: true,
45
+ });
46
+ projectType = useDetected ? detection.detected : await askProjectType();
47
+ }
48
+ else {
49
+ log.warn("Could not auto-detect project type.");
50
+ projectType = yes ? "node" : await askProjectType();
51
+ if (yes) {
52
+ log.info(`Defaulting to ${pc.bold(projectType)}`);
53
+ }
54
+ }
55
+ }
56
+ // Step 3: Detect package manager
57
+ const pm = detectPackageManager(cwd);
58
+ log.info(`Package manager: ${pc.bold(pm)}`);
59
+ // Step 4: Create eslint.config.mts
60
+ const eslintConfigPath = resolve(cwd, "eslint.config.mts");
61
+ if (existsSync(eslintConfigPath)) {
62
+ const overwrite = yes
63
+ ? true
64
+ : await confirm({
65
+ message: "eslint.config.mts already exists. Overwrite?",
66
+ default: false,
67
+ });
68
+ if (!overwrite) {
69
+ log.warn("Skipping eslint.config.mts");
70
+ }
71
+ else {
72
+ await writeFile(eslintConfigPath, getEslintConfigContent(projectType));
73
+ log.success("Created eslint.config.mts");
74
+ }
75
+ }
76
+ else {
77
+ await writeFile(eslintConfigPath, getEslintConfigContent(projectType));
78
+ log.success("Created eslint.config.mts");
79
+ }
80
+ // Step 5: Optionally create .prettierrc.json
81
+ const prettierRcPath = resolve(cwd, ".prettierrc.json");
82
+ if (allowPrettier) {
83
+ if (!existsSync(prettierRcPath)) {
84
+ const createPrettierRc = yes
85
+ ? true
86
+ : await confirm({
87
+ message: "Create .prettierrc.json? (mirrors config defaults)",
88
+ default: true,
89
+ });
90
+ if (createPrettierRc) {
91
+ await writeFile(prettierRcPath, getPrettierRcContent());
92
+ log.success("Created .prettierrc.json");
93
+ }
94
+ }
95
+ else {
96
+ log.info(".prettierrc.json already exists, skipping.");
97
+ }
98
+ }
99
+ else {
100
+ log.info("Skipping .prettierrc.json (--no-prettier)");
101
+ }
102
+ // Step 6: Optionally create editor settings for VSCode
103
+ const vscodeDir = resolve(cwd, ".vscode");
104
+ const vscodeSettingsPath = resolve(vscodeDir, "settings.json");
105
+ const ensureVscodeSettings = async () => {
106
+ try {
107
+ await mkdir(vscodeDir, { recursive: true });
108
+ }
109
+ catch {
110
+ // ignore
111
+ }
112
+ };
113
+ if (!existsSync(vscodeSettingsPath)) {
114
+ await ensureVscodeSettings();
115
+ await writeFile(vscodeSettingsPath, getEditorSettingsContent());
116
+ log.success("Created .vscode/settings.json (format on save)");
117
+ }
118
+ else if (yes) {
119
+ await ensureVscodeSettings();
120
+ await writeFile(vscodeSettingsPath, getEditorSettingsContent());
121
+ log.success("Overwrote .vscode/settings.json with default editor preferences");
122
+ }
123
+ else {
124
+ log.info(".vscode/settings.json already exists, skipping editor config");
125
+ }
126
+ // Step 6: Update package.json scripts
127
+ const pkgRaw = await readFile(pkgPath, "utf-8");
128
+ const pkg = JSON.parse(pkgRaw);
129
+ const newScripts = getScripts();
130
+ const existingScripts = pkg.scripts ?? {};
131
+ let scriptsAdded = 0;
132
+ for (const [key, value] of Object.entries(newScripts)) {
133
+ if (!(key in existingScripts)) {
134
+ existingScripts[key] = value;
135
+ scriptsAdded++;
136
+ }
137
+ }
138
+ if (scriptsAdded > 0) {
139
+ pkg.scripts = existingScripts;
140
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
141
+ log.success(`Added ${scriptsAdded} script(s) to package.json`);
142
+ }
143
+ else {
144
+ log.info("All lint/format scripts already exist in package.json");
145
+ }
146
+ // Step 7: Install dependencies
147
+ const depsToInstall = getDependencies();
148
+ console.log();
149
+ log.step("Installing dependencies...");
150
+ log.info(pc.dim(depsToInstall.join(", ")));
151
+ const installCmd = getInstallCommand(pm, depsToInstall, true);
152
+ log.info(pc.dim(`$ ${installCmd}`));
153
+ const result = await exec(installCmd, cwd);
154
+ if (result.code !== 0) {
155
+ log.error("Installation failed:");
156
+ console.error(result.stderr);
157
+ process.exit(1);
158
+ }
159
+ log.success("Dependencies installed");
160
+ // Done
161
+ console.log();
162
+ log.success(pc.bold("ESLint + Prettier configured!"));
163
+ console.log();
164
+ const runPrefix = pm === "npm" ? "npm run" : pm;
165
+ console.log(` ${pc.dim("$")} ${runPrefix} lint`);
166
+ console.log(` ${pc.dim("$")} ${runPrefix} lint:fix`);
167
+ console.log(` ${pc.dim("$")} ${runPrefix} format`);
168
+ console.log();
169
+ }
170
+ async function askProjectType() {
171
+ return select({
172
+ message: "What type of project is this?",
173
+ choices: [
174
+ {
175
+ name: "React + Vite (SPA)",
176
+ value: "react-vite",
177
+ description: "Browser app with React and Vite",
178
+ },
179
+ {
180
+ name: "React Library",
181
+ value: "react-lib",
182
+ description: "Publishable React component library",
183
+ },
184
+ {
185
+ name: "Node.js / CLI",
186
+ value: "node",
187
+ description: "Server, API, CLI tool, or library",
188
+ },
189
+ ],
190
+ });
191
+ }
192
+ function getDependencies() {
193
+ return [
194
+ "@promakeai/eslint-config",
195
+ "eslint",
196
+ "prettier",
197
+ "typescript",
198
+ "jiti",
199
+ ];
200
+ }
package/dist/index.js ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import { init } from "./commands/init.js";
3
+ import pc from "picocolors";
4
+ const args = process.argv.slice(2);
5
+ const command = args[0];
6
+ function parseInitOptions(argv) {
7
+ const options = {
8
+ yes: false,
9
+ prettier: true,
10
+ };
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const arg = argv[i];
13
+ if (arg === "--yes" || arg === "-y") {
14
+ options.yes = true;
15
+ }
16
+ else if (arg === "--no-prettier") {
17
+ options.prettier = false;
18
+ }
19
+ else if (arg === "--project" || arg === "-p") {
20
+ const value = argv[i + 1];
21
+ if (!value) {
22
+ throw new Error("Missing value for --project");
23
+ }
24
+ options.projectType = value;
25
+ i++;
26
+ }
27
+ }
28
+ return options;
29
+ }
30
+ async function main() {
31
+ if (!command || command === "init") {
32
+ const opts = parseInitOptions(args.slice(command ? 1 : 0));
33
+ await init(opts);
34
+ }
35
+ else if (command === "--help" || command === "-h") {
36
+ printHelp();
37
+ }
38
+ else if (command === "--version" || command === "-v") {
39
+ console.log("0.1.0");
40
+ }
41
+ else {
42
+ console.error(pc.red(`Unknown command: ${command}`));
43
+ printHelp();
44
+ process.exit(1);
45
+ }
46
+ }
47
+ function printHelp() {
48
+ console.log(`
49
+ ${pc.bold("@promakeai/eslint")} - ESLint + Prettier scaffolding tool
50
+
51
+ ${pc.bold("Usage:")}
52
+ npx @promakeai/eslint init [options] Set up ESLint + Prettier in your project
53
+
54
+ ${pc.bold("Options:")}
55
+ -y, --yes Accept all prompts (overwrite files, create .prettierrc)
56
+ -p, --project <type> One of: react-vite | react-lib | node
57
+ --no-prettier Skip creating .prettierrc.json
58
+ -h, --help Show this help message
59
+ -v, --version Show version number
60
+ `);
61
+ }
62
+ main().catch((err) => {
63
+ console.error(pc.red("Error:"), err.message);
64
+ process.exit(1);
65
+ });
@@ -0,0 +1,39 @@
1
+ import { existsSync } from "node:fs";
2
+ import { readFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ export async function detectProjectType(cwd) {
5
+ const viteConfigExists = existsSync(resolve(cwd, "vite.config.ts")) ||
6
+ existsSync(resolve(cwd, "vite.config.js")) ||
7
+ existsSync(resolve(cwd, "vite.config.mts"));
8
+ let pkg = {};
9
+ try {
10
+ const raw = await readFile(resolve(cwd, "package.json"), "utf-8");
11
+ pkg = JSON.parse(raw);
12
+ }
13
+ catch {
14
+ return { detected: null, confidence: "No package.json found" };
15
+ }
16
+ const allDeps = {
17
+ ...(pkg.dependencies ?? {}),
18
+ ...(pkg.devDependencies ?? {}),
19
+ };
20
+ const hasReact = "react" in allDeps;
21
+ const hasVite = "vite" in allDeps;
22
+ const hasNext = "next" in allDeps;
23
+ if (hasReact && (hasVite || viteConfigExists)) {
24
+ return { detected: "react-vite", confidence: "Found react + vite" };
25
+ }
26
+ if (hasReact && !hasVite && !hasNext) {
27
+ return {
28
+ detected: "react-lib",
29
+ confidence: "Found react without vite/next",
30
+ };
31
+ }
32
+ if (!hasReact) {
33
+ return {
34
+ detected: "node",
35
+ confidence: "No browser framework detected",
36
+ };
37
+ }
38
+ return { detected: null, confidence: "Could not determine project type" };
39
+ }
@@ -0,0 +1,21 @@
1
+ import { spawn } from "node:child_process";
2
+ export function exec(command, cwd) {
3
+ return new Promise((resolve) => {
4
+ const child = spawn(command, {
5
+ cwd,
6
+ shell: true,
7
+ stdio: ["ignore", "pipe", "pipe"],
8
+ });
9
+ let stdout = "";
10
+ let stderr = "";
11
+ child.stdout.on("data", (data) => {
12
+ stdout += data.toString();
13
+ });
14
+ child.stderr.on("data", (data) => {
15
+ stderr += data.toString();
16
+ });
17
+ child.on("close", (code) => {
18
+ resolve({ code: code ?? 1, stdout, stderr });
19
+ });
20
+ });
21
+ }
@@ -0,0 +1,8 @@
1
+ import pc from "picocolors";
2
+ export const log = {
3
+ info: (msg) => console.log(pc.cyan("i"), msg),
4
+ success: (msg) => console.log(pc.green("\u2713"), msg),
5
+ warn: (msg) => console.log(pc.yellow("!"), msg),
6
+ error: (msg) => console.error(pc.red("\u2717"), msg),
7
+ step: (msg) => console.log(pc.bold(pc.blue(">")), msg),
8
+ };
@@ -0,0 +1,29 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ export function detectPackageManager(cwd) {
4
+ if (existsSync(resolve(cwd, "bun.lock")) ||
5
+ existsSync(resolve(cwd, "bun.lockb"))) {
6
+ return "bun";
7
+ }
8
+ if (existsSync(resolve(cwd, "pnpm-lock.yaml"))) {
9
+ return "pnpm";
10
+ }
11
+ if (existsSync(resolve(cwd, "yarn.lock"))) {
12
+ return "yarn";
13
+ }
14
+ return "npm";
15
+ }
16
+ export function getInstallCommand(pm, packages, dev) {
17
+ const pkgs = packages.join(" ");
18
+ switch (pm) {
19
+ case "bun":
20
+ return `bun add${dev ? " -D" : ""} ${pkgs}`;
21
+ case "pnpm":
22
+ return `pnpm add${dev ? " -D" : ""} ${pkgs}`;
23
+ case "yarn":
24
+ return `yarn add${dev ? " -D" : ""} ${pkgs}`;
25
+ case "npm":
26
+ default:
27
+ return `npm install${dev ? " --save-dev" : ""} ${pkgs}`;
28
+ }
29
+ }
@@ -0,0 +1,45 @@
1
+ export function getEslintConfigContent(projectType) {
2
+ switch (projectType) {
3
+ case "react-vite":
4
+ case "react-lib":
5
+ return `import config from "@promakeai/eslint-config/react";
6
+
7
+ export default config;
8
+ `;
9
+ case "node":
10
+ return `import config from "@promakeai/eslint-config/node";
11
+
12
+ export default config;
13
+ `;
14
+ }
15
+ }
16
+ export function getPrettierRcContent() {
17
+ return (JSON.stringify({
18
+ semi: true,
19
+ singleQuote: false,
20
+ tabWidth: 2,
21
+ trailingComma: "all",
22
+ printWidth: 80,
23
+ bracketSpacing: true,
24
+ arrowParens: "always",
25
+ endOfLine: "auto",
26
+ }, null, 2) + "\n");
27
+ }
28
+ export function getScripts() {
29
+ return {
30
+ lint: "eslint .",
31
+ "lint:fix": "eslint . --fix",
32
+ format: "prettier --write .",
33
+ "format:check": "prettier --check .",
34
+ };
35
+ }
36
+ export function getEditorSettingsContent() {
37
+ return JSON.stringify({
38
+ "editor.formatOnSave": true,
39
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
40
+ "editor.codeActionsOnSave": {
41
+ "source.fixAll.eslint": true,
42
+ },
43
+ "files.autoSave": "off",
44
+ }, null, 2) + "\n";
45
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@promakeai/eslint",
3
+ "version": "0.1.0",
4
+ "description": "CLI to scaffold ESLint + Prettier with @promakeai/eslint-config",
5
+ "type": "module",
6
+ "bin": {
7
+ "@promakeai/eslint": "./dist/index.js",
8
+ "promake-eslint": "./dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc --project tsconfig.build.json",
15
+ "clean": "rm -rf dist",
16
+ "dev": "bun run src/index.ts",
17
+ "pack": "bun run clean && bun run build && npm pack --dry-run",
18
+ "publish": "bun run clean && bun run build && npm publish --access public",
19
+ "release": "npm run publish",
20
+ "prepublishOnly": "bun run clean && bun run build"
21
+ },
22
+ "keywords": [
23
+ "eslint",
24
+ "prettier",
25
+ "cli",
26
+ "scaffolding",
27
+ "typescript"
28
+ ],
29
+ "author": "Promake",
30
+ "license": "MIT",
31
+ "engines": {
32
+ "node": "^20.19.0 || ^22.13.0 || >=24"
33
+ },
34
+ "dependencies": {
35
+ "@inquirer/prompts": "^8.2.0",
36
+ "picocolors": "^1.1.1"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^25.2.3",
40
+ "typescript": "^5.9.3"
41
+ },
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }