@bottomlessmargaritas/formatting-configs 1.0.0 → 2.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.
Files changed (2) hide show
  1. package/bin/cli.js +67 -72
  2. package/package.json +15 -7
package/bin/cli.js CHANGED
@@ -3,31 +3,37 @@
3
3
  import {
4
4
  existsSync,
5
5
  copyFileSync,
6
- mkdirSync,
7
6
  readFileSync,
8
7
  writeFileSync,
9
8
  readdirSync,
10
- renameSync,
9
+ unlinkSync,
11
10
  } from "node:fs";
12
- import { dirname, join, resolve } from "node:path";
11
+ import { dirname, join, resolve, extname, basename } from "node:path";
13
12
  import { fileURLToPath } from "node:url";
14
13
  import { execSync } from "node:child_process";
15
- import { createInterface } from "node:readline";
16
14
 
17
15
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
16
  const CONFIGS_DIR = resolve(__dirname, "..", "configs");
19
17
 
20
- const isAuto = process.argv.includes("--auto");
21
18
  const isDryRun = process.argv.includes("--dry-run");
22
19
  const skipInstall = process.argv.includes("--skip-install");
23
20
 
24
21
  const PKG_NAME = "@bottomlessmargaritas/formatting-configs";
22
+ const NAMESPACE = "bottomlessmargaritas";
25
23
 
24
+ // Namespaced filenames: prettier.config.js → prettier.config.bottomlessmargaritas.js
25
+ function namespacedFileName(file) {
26
+ const ext = extname(file);
27
+ const base = basename(file, ext);
28
+ return `${base}.${NAMESPACE}${ext}`;
29
+ }
30
+
31
+ // Scripts reference the namespaced config files explicitly
26
32
  const SCRIPTS_TO_ADD = {
27
- format: 'prettier --write "**/*.{js,jsx,ts,tsx,json,css,scss,md}"',
28
- "format:check": 'prettier --check "**/*.{js,jsx,ts,tsx,json,css,scss,md}"',
29
- lint: "eslint .",
30
- "lint:fix": "eslint . --fix",
33
+ [`${NAMESPACE}:format`]: `prettier --config ${namespacedFileName("prettier.config.js")} --write "**/*.{js,jsx,ts,tsx,json,css,scss,md}"`,
34
+ [`${NAMESPACE}:format:check`]: `prettier --config ${namespacedFileName("prettier.config.js")} --check "**/*.{js,jsx,ts,tsx,json,css,scss,md}"`,
35
+ [`${NAMESPACE}:lint`]: `eslint --config ${namespacedFileName("eslint.config.js")} .`,
36
+ [`${NAMESPACE}:lint:fix`]: `eslint --config ${namespacedFileName("eslint.config.js")} . --fix`,
31
37
  };
32
38
 
33
39
  const PEER_DEPS = {
@@ -42,7 +48,6 @@ const PEER_DEPS = {
42
48
  "typescript-eslint": "^8",
43
49
  };
44
50
 
45
- // React-related deps — only installed if project uses React
46
51
  const REACT_PEER_DEPS = {
47
52
  "@babel/eslint-parser": "^7",
48
53
  "eslint-plugin-jsx-a11y": "^6",
@@ -61,11 +66,6 @@ function findProjectRoot(startDir) {
61
66
  return null;
62
67
  }
63
68
 
64
- function getBackupName(filePath) {
65
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
66
- return `${filePath}.backup-${timestamp}`;
67
- }
68
-
69
69
  function detectPackageManager(projectRoot) {
70
70
  if (existsSync(join(projectRoot, "pnpm-lock.yaml"))) {
71
71
  return "pnpm";
@@ -89,25 +89,16 @@ function projectUsesReact(projectRoot) {
89
89
  }
90
90
  }
91
91
 
92
- async function prompt(question) {
93
- const rl = createInterface({ input: process.stdin, output: process.stdout });
94
- return new Promise((resolve) => {
95
- rl.question(question, (answer) => {
96
- rl.close();
97
- resolve(answer.trim().toLowerCase());
98
- });
99
- });
100
- }
101
-
102
- async function copyConfigFiles(projectRoot) {
92
+ function copyConfigFiles(projectRoot) {
103
93
  const configFiles = readdirSync(CONFIGS_DIR);
94
+ const currentNamespacedFiles = new Set(configFiles.map(namespacedFileName));
104
95
  let copied = 0;
105
- let backed = 0;
106
96
  let skipped = 0;
107
97
 
108
98
  for (const file of configFiles) {
109
99
  const source = join(CONFIGS_DIR, file);
110
- const target = join(projectRoot, file);
100
+ const targetName = namespacedFileName(file);
101
+ const target = join(projectRoot, targetName);
111
102
 
112
103
  if (existsSync(target)) {
113
104
  const sourceContent = readFileSync(source, "utf8");
@@ -117,66 +108,73 @@ async function copyConfigFiles(projectRoot) {
117
108
  skipped++;
118
109
  continue;
119
110
  }
111
+ }
120
112
 
121
- if (isAuto) {
122
- const backupPath = getBackupName(target);
123
- if (!isDryRun) {
124
- renameSync(target, backupPath);
125
- copyFileSync(source, target);
126
- console.log(` ↪ Backed up existing ${file} → ${backupPath.split("/").pop()}`);
127
- }
128
- backed++;
129
- copied++;
130
- } else {
131
- const answer = await prompt(` ${file} already exists and differs. Overwrite? (y/n) `);
132
- if (answer !== "y") {
133
- skipped++;
134
- continue;
135
- }
136
-
137
- const backupPath = getBackupName(target);
138
- if (!isDryRun) {
139
- renameSync(target, backupPath);
140
- copyFileSync(source, target);
141
- console.log(` ↪ Backed up → ${backupPath.split("/").pop()}`);
142
- }
143
- backed++;
144
- copied++;
145
- }
113
+ if (isDryRun) {
114
+ console.log(`[dry-run] Would write: ${targetName}`);
146
115
  } else {
147
- if (!isDryRun) {
148
- copyFileSync(source, target);
116
+ copyFileSync(source, target);
117
+ }
118
+ copied++;
119
+ }
120
+
121
+ // Remove stale namespaced config files from previous versions
122
+ const namespacedPattern = `.${NAMESPACE}.`;
123
+ let removed = 0;
124
+
125
+ for (const file of readdirSync(projectRoot)) {
126
+ if (file.includes(namespacedPattern) && !currentNamespacedFiles.has(file)) {
127
+ if (isDryRun) {
128
+ console.log(`[dry-run] Would remove stale config: ${file}`);
129
+ } else {
130
+ unlinkSync(join(projectRoot, file));
149
131
  }
150
- copied++;
132
+ removed++;
151
133
  }
152
134
  }
153
135
 
154
- console.log(`${PKG_NAME}: ${copied} config(s) copied, ${backed} backed up, ${skipped} unchanged`);
136
+ console.log(`${PKG_NAME}: ${copied} config(s) written, ${skipped} unchanged, ${removed} stale removed`);
155
137
  }
156
138
 
157
139
  function updatePackageJsonScripts(projectRoot) {
158
140
  const pkgPath = join(projectRoot, "package.json");
159
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
141
+ const raw = readFileSync(pkgPath, "utf8");
142
+ const pkg = JSON.parse(raw);
160
143
 
161
144
  if (!pkg.scripts) {
162
145
  pkg.scripts = {};
163
146
  }
164
147
 
148
+ const currentScriptNames = new Set(Object.keys(SCRIPTS_TO_ADD));
165
149
  let added = 0;
150
+ let removed = 0;
151
+
152
+ // Add/update current scripts
166
153
  for (const [name, command] of Object.entries(SCRIPTS_TO_ADD)) {
167
- if (!pkg.scripts[name]) {
154
+ if (pkg.scripts[name] !== command) {
168
155
  pkg.scripts[name] = command;
169
156
  added++;
170
157
  }
171
158
  }
172
159
 
173
- if (added > 0) {
174
- if (!isDryRun) {
160
+ // Remove stale namespaced scripts from previous versions
161
+ for (const name of Object.keys(pkg.scripts)) {
162
+ if (name.startsWith(`${NAMESPACE}:`) && !currentScriptNames.has(name)) {
163
+ delete pkg.scripts[name];
164
+ removed++;
165
+ }
166
+ }
167
+
168
+ if (added > 0 || removed > 0) {
169
+ if (isDryRun) {
170
+ if (added > 0) console.log(`[dry-run] Would add/update ${added} script(s) in package.json`);
171
+ if (removed > 0) console.log(`[dry-run] Would remove ${removed} stale script(s) from package.json`);
172
+ } else {
175
173
  writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + "\n", "utf8");
176
174
  }
177
- console.log(`${PKG_NAME}: Added ${added} script(s) to package.json (format, format:check, lint, lint:fix)`);
175
+ console.log(`${PKG_NAME}: ${added} script(s) added/updated, ${removed} stale removed in package.json`);
178
176
  } else {
179
- console.log(`${PKG_NAME}: All formatting scripts already present in package.json`);
177
+ console.log(`${PKG_NAME}: All namespaced scripts already up to date in package.json`);
180
178
  }
181
179
  }
182
180
 
@@ -205,18 +203,18 @@ function installMissingPeers(projectRoot) {
205
203
 
206
204
  console.log(`${PKG_NAME}: Installing ${missing.length} missing peer dep(s) with ${pm}...`);
207
205
 
208
- if (!isDryRun) {
206
+ if (isDryRun) {
207
+ console.log(`[dry-run] Would run: ${installCmd}`);
208
+ } else {
209
209
  try {
210
210
  execSync(installCmd, { cwd: projectRoot, stdio: "inherit" });
211
211
  } catch {
212
212
  console.warn(`${PKG_NAME}: Peer dependency install failed. Run manually:\n ${installCmd}`);
213
213
  }
214
- } else {
215
- console.log(`[dry-run] Would run: ${installCmd}`);
216
214
  }
217
215
  }
218
216
 
219
- async function run() {
217
+ function run() {
220
218
  const cwd = process.env.INIT_CWD || process.cwd();
221
219
  const projectRoot = findProjectRoot(cwd);
222
220
 
@@ -225,7 +223,7 @@ async function run() {
225
223
  process.exit(1);
226
224
  }
227
225
 
228
- await copyConfigFiles(projectRoot);
226
+ copyConfigFiles(projectRoot);
229
227
  updatePackageJsonScripts(projectRoot);
230
228
 
231
229
  if (!skipInstall) {
@@ -233,7 +231,4 @@ async function run() {
233
231
  }
234
232
  }
235
233
 
236
- run().catch((err) => {
237
- console.error(`${PKG_NAME}:`, err.message);
238
- process.exit(1);
239
- });
234
+ run();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bottomlessmargaritas/formatting-configs",
3
- "version": "1.0.0",
4
- "description": "Canonical Prettier and ESLint configs for @nullvoidundefined projects",
3
+ "version": "2.1.0",
4
+ "description": "Canonical Prettier and ESLint configs for @bottomlessmargaritas projects",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "formatting-configs": "./bin/cli.js"
@@ -29,10 +29,18 @@
29
29
  "typescript-eslint": ">=8"
30
30
  },
31
31
  "peerDependenciesMeta": {
32
- "@babel/eslint-parser": { "optional": true },
33
- "eslint-plugin-jsx-a11y": { "optional": true },
34
- "eslint-plugin-react": { "optional": true },
35
- "eslint-plugin-react-hooks": { "optional": true }
32
+ "@babel/eslint-parser": {
33
+ "optional": true
34
+ },
35
+ "eslint-plugin-jsx-a11y": {
36
+ "optional": true
37
+ },
38
+ "eslint-plugin-react": {
39
+ "optional": true
40
+ },
41
+ "eslint-plugin-react-hooks": {
42
+ "optional": true
43
+ }
36
44
  },
37
45
  "keywords": [
38
46
  "prettier",
@@ -40,7 +48,7 @@
40
48
  "formatting",
41
49
  "config"
42
50
  ],
43
- "author": "nullvoidundefined",
51
+ "author": "bottomlessmargaritas",
44
52
  "license": "MIT",
45
53
  "engines": {
46
54
  "node": ">=18"