@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.
- package/bin/cli.js +67 -72
- 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
|
-
|
|
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:
|
|
28
|
-
|
|
29
|
-
lint: "eslint
|
|
30
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
|
|
132
|
+
removed++;
|
|
151
133
|
}
|
|
152
134
|
}
|
|
153
135
|
|
|
154
|
-
console.log(`${PKG_NAME}: ${copied} config(s)
|
|
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
|
|
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 (
|
|
154
|
+
if (pkg.scripts[name] !== command) {
|
|
168
155
|
pkg.scripts[name] = command;
|
|
169
156
|
added++;
|
|
170
157
|
}
|
|
171
158
|
}
|
|
172
159
|
|
|
173
|
-
|
|
174
|
-
|
|
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}:
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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()
|
|
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
|
|
4
|
-
"description": "Canonical Prettier and ESLint configs for @
|
|
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": {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"eslint-plugin-
|
|
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": "
|
|
51
|
+
"author": "bottomlessmargaritas",
|
|
44
52
|
"license": "MIT",
|
|
45
53
|
"engines": {
|
|
46
54
|
"node": ">=18"
|