@gesslar/uglier 0.5.0 → 0.6.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/README.md +36 -9
- package/bin/cli.js +633 -0
- package/package.json +7 -6
- package/src/install.js +58 -0
- package/bin/install.js +0 -334
package/README.md
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
**Opinionated, composable ESLint flat config for people who like their code readable.**
|
|
4
4
|
|
|
5
|
-
A flexible ESLint configuration system built on
|
|
5
|
+
A flexible ESLint configuration system built on
|
|
6
|
+
[flat config](https://eslint.org/docs/latest/use/configure/configuration-files)
|
|
7
|
+
that lets you mix and match stylistic rules, JSDoc enforcement, and environment
|
|
8
|
+
presets.
|
|
6
9
|
|
|
7
10
|
## Monotribe
|
|
8
11
|
|
|
@@ -30,14 +33,16 @@ kthx
|
|
|
30
33
|
## Quick Start
|
|
31
34
|
|
|
32
35
|
```bash
|
|
33
|
-
# Install and
|
|
36
|
+
# Install dependencies and generate config in one command
|
|
37
|
+
npx @gesslar/uglier install
|
|
34
38
|
npx @gesslar/uglier init node
|
|
35
39
|
|
|
36
40
|
# Or for React projects
|
|
41
|
+
npx @gesslar/uglier install
|
|
37
42
|
npx @gesslar/uglier init react
|
|
38
43
|
|
|
39
|
-
#
|
|
40
|
-
npx @gesslar/uglier
|
|
44
|
+
# Forgot to add React later? No problem!
|
|
45
|
+
npx @gesslar/uglier add react
|
|
41
46
|
```
|
|
42
47
|
|
|
43
48
|
This automatically installs `@gesslar/uglier`, `eslint`, and all dependencies.
|
|
@@ -156,8 +161,8 @@ Run `npx @gesslar/uglier --help` to see all available configs with descriptions.
|
|
|
156
161
|
## Commands
|
|
157
162
|
|
|
158
163
|
```bash
|
|
159
|
-
# Install dependencies
|
|
160
|
-
npx @gesslar/uglier
|
|
164
|
+
# Install dependencies
|
|
165
|
+
npx @gesslar/uglier install
|
|
161
166
|
|
|
162
167
|
# Generate config for specific targets
|
|
163
168
|
npx @gesslar/uglier init node
|
|
@@ -165,13 +170,22 @@ npx @gesslar/uglier init web
|
|
|
165
170
|
npx @gesslar/uglier init react
|
|
166
171
|
npx @gesslar/uglier init node web # Multiple targets
|
|
167
172
|
|
|
173
|
+
# Add config blocks to existing eslint.config.js
|
|
174
|
+
npx @gesslar/uglier add react
|
|
175
|
+
npx @gesslar/uglier add tauri vscode-extension # Multiple targets
|
|
176
|
+
|
|
177
|
+
# Remove config blocks from existing eslint.config.js
|
|
178
|
+
npx @gesslar/uglier remove react
|
|
179
|
+
npx @gesslar/uglier remove web tauri # Multiple targets
|
|
180
|
+
# Note: Also removes any overrides for removed targets
|
|
181
|
+
|
|
168
182
|
# Show available configs
|
|
169
183
|
npx @gesslar/uglier --help
|
|
170
184
|
|
|
171
185
|
# Works with any package manager
|
|
172
|
-
pnpx @gesslar/uglier
|
|
173
|
-
yarn dlx @gesslar/uglier
|
|
174
|
-
bunx @gesslar/uglier
|
|
186
|
+
pnpx @gesslar/uglier install # pnpm
|
|
187
|
+
yarn dlx @gesslar/uglier install # yarn
|
|
188
|
+
bunx @gesslar/uglier install # bun
|
|
175
189
|
```
|
|
176
190
|
|
|
177
191
|
## Manual Installation
|
|
@@ -194,6 +208,19 @@ bun add -d @gesslar/uglier eslint
|
|
|
194
208
|
|
|
195
209
|
Note: `@stylistic/eslint-plugin`, `eslint-plugin-jsdoc`, and `globals` are bundled as dependencies.
|
|
196
210
|
|
|
211
|
+
## Development
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Run tests
|
|
215
|
+
npm test
|
|
216
|
+
|
|
217
|
+
# Run linter
|
|
218
|
+
npm run lint
|
|
219
|
+
|
|
220
|
+
# Fix linting issues
|
|
221
|
+
npm run lint:fix
|
|
222
|
+
```
|
|
223
|
+
|
|
197
224
|
## Philosophy
|
|
198
225
|
|
|
199
226
|
This config enforces:
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file cli.js - Core CLI functions for @gesslar/uglier
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {execSync} from "child_process"
|
|
6
|
+
import {
|
|
7
|
+
FileObject,
|
|
8
|
+
DirectoryObject,
|
|
9
|
+
CappedDirectoryObject,
|
|
10
|
+
Sass
|
|
11
|
+
} from "@gesslar/toolkit"
|
|
12
|
+
import c from "@gesslar/colours"
|
|
13
|
+
import {detectAgent} from "@skarab/detect-package-manager"
|
|
14
|
+
|
|
15
|
+
// Use CappedDirectoryObject to ensure we never accidentally venture outside project
|
|
16
|
+
// Cap at project root - this becomes our sandbox boundary
|
|
17
|
+
const PROJECT_ROOT = CappedDirectoryObject.fromCwd()
|
|
18
|
+
const SRC_DIR = PROJECT_ROOT.getDirectory("src")
|
|
19
|
+
const PACKAGE_NAME = "@gesslar/uglier"
|
|
20
|
+
|
|
21
|
+
// Only peer dependencies need to be installed separately
|
|
22
|
+
// (all other dependencies come bundled with the package)
|
|
23
|
+
const PEER_DEPS = [
|
|
24
|
+
"eslint"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse targets from config file's with array
|
|
29
|
+
*
|
|
30
|
+
* @param {string} content - File content
|
|
31
|
+
* @returns {string[]} Array of target names
|
|
32
|
+
*/
|
|
33
|
+
function parseTargetsFromConfig(content) {
|
|
34
|
+
// Match the entire with array, being careful not to stop at nested brackets
|
|
35
|
+
const withMatch = content.match(/with:\s*\[([\s\S]*?)\n\s*\]/m)
|
|
36
|
+
|
|
37
|
+
if(!withMatch) {
|
|
38
|
+
return []
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extract all quoted strings that appear at the start of lines (config names)
|
|
42
|
+
const targets = []
|
|
43
|
+
const lines = withMatch[1].split("\n")
|
|
44
|
+
|
|
45
|
+
for(const line of lines) {
|
|
46
|
+
// Match either single or double quoted strings at start of line
|
|
47
|
+
const match = line.match(/^\s*(['"])([^'"]+)\1/)
|
|
48
|
+
|
|
49
|
+
if(match) {
|
|
50
|
+
targets.push(match[2])
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return targets
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get install command for detected package manager
|
|
59
|
+
*
|
|
60
|
+
* @returns {Promise<{manager: string, installCmd: string}>} Package manager info
|
|
61
|
+
*/
|
|
62
|
+
export async function getPackageManagerInfo() {
|
|
63
|
+
const agent = await detectAgent()
|
|
64
|
+
const manager = agent?.name || "npm"
|
|
65
|
+
|
|
66
|
+
const commands = {
|
|
67
|
+
npm: "npm i -D",
|
|
68
|
+
pnpm: "pnpm i -D",
|
|
69
|
+
yarn: "yarn add -D",
|
|
70
|
+
bun: "bun add -d"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
manager,
|
|
75
|
+
installCmd: commands[manager] || commands.npm
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get the appropriate eslint run command for the detected package manager
|
|
81
|
+
*
|
|
82
|
+
* @returns {Promise<string>} ESLint command (e.g., "pnpm eslint .")
|
|
83
|
+
*/
|
|
84
|
+
async function getEslintCommand() {
|
|
85
|
+
const {manager} = await getPackageManagerInfo()
|
|
86
|
+
|
|
87
|
+
const commands = {
|
|
88
|
+
npm: "npx eslint .",
|
|
89
|
+
pnpm: "pnpm eslint .",
|
|
90
|
+
yarn: "yarn eslint .",
|
|
91
|
+
bun: "bunx eslint ."
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return commands[manager] || commands.npm
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Execute a command and return output
|
|
99
|
+
*
|
|
100
|
+
* @param {string} cmd - Command to execute
|
|
101
|
+
* @returns {string} Command output
|
|
102
|
+
* @throws {Sass} Enhanced error with command context
|
|
103
|
+
*/
|
|
104
|
+
export function exec(cmd) {
|
|
105
|
+
try {
|
|
106
|
+
return execSync(cmd, {encoding: "utf8", stdio: "pipe"})
|
|
107
|
+
} catch(error) {
|
|
108
|
+
const err = new Sass(`Failed to execute command: ${cmd}`, {cause: error})
|
|
109
|
+
err.addTrace("Command execution failed")
|
|
110
|
+
console.error(err.report())
|
|
111
|
+
process.exit(1)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get available configs from the source file
|
|
117
|
+
*
|
|
118
|
+
* @returns {Promise<Array<{name: string, description: string, files: string}>|null>} Available configs
|
|
119
|
+
*/
|
|
120
|
+
export async function getAvailableConfigs() {
|
|
121
|
+
try {
|
|
122
|
+
// Try to read from installed package or local source
|
|
123
|
+
const cwd = new CappedDirectoryObject(process.cwd())
|
|
124
|
+
const installedDir = new DirectoryObject(`node_modules/${PACKAGE_NAME}/src`, cwd)
|
|
125
|
+
|
|
126
|
+
const localSource = new FileObject("uglier.js", SRC_DIR)
|
|
127
|
+
const installedSource = new FileObject("uglier.js", installedDir)
|
|
128
|
+
|
|
129
|
+
let uglierFile = null
|
|
130
|
+
|
|
131
|
+
if(await localSource.exists) {
|
|
132
|
+
uglierFile = localSource
|
|
133
|
+
} else if(await installedSource.exists) {
|
|
134
|
+
uglierFile = installedSource
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if(!uglierFile) {
|
|
138
|
+
return null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const source = await uglierFile.read()
|
|
142
|
+
|
|
143
|
+
// Extract config names, descriptions, and default files
|
|
144
|
+
const configs = []
|
|
145
|
+
// Match individual config blocks within CONFIGS object
|
|
146
|
+
const configBlockRegex = /\/\*\*\s*\n\s*\*\s*([^\n@*]+?)\s*\n(?:\s*\*[^\n]*\n)*?\s*\*\/\s*\n\s*["']([^"']+)["']:\s*\([^)]*\)\s*=>\s*\{[^}]*?files\s*=\s*(\[[^\]]+\])/g
|
|
147
|
+
let match
|
|
148
|
+
|
|
149
|
+
while((match = configBlockRegex.exec(source)) !== null) {
|
|
150
|
+
configs.push({
|
|
151
|
+
name: match[2],
|
|
152
|
+
description: match[1].trim(),
|
|
153
|
+
files: match[3]
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return configs
|
|
158
|
+
} catch {
|
|
159
|
+
return null
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Check if a package is already installed
|
|
165
|
+
*
|
|
166
|
+
* @param {string} packageName - Name of package to check
|
|
167
|
+
* @returns {Promise<boolean>} True if installed
|
|
168
|
+
*/
|
|
169
|
+
export async function isInstalled(packageName) {
|
|
170
|
+
try {
|
|
171
|
+
const cwd = new CappedDirectoryObject(process.cwd())
|
|
172
|
+
const packageJsonFile = new FileObject("package.json", cwd)
|
|
173
|
+
|
|
174
|
+
if(!(await packageJsonFile.exists)) {
|
|
175
|
+
console.warn(c`No {<B}package.json{B>} found. Please initialize your project first.`)
|
|
176
|
+
process.exit(1)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const packageJson = await packageJsonFile.loadData("json")
|
|
180
|
+
const allDeps = {
|
|
181
|
+
...packageJson.dependencies,
|
|
182
|
+
...packageJson.devDependencies,
|
|
183
|
+
...packageJson.peerDependencies
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return packageName in allDeps
|
|
187
|
+
} catch {
|
|
188
|
+
return false
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Main installation routine
|
|
194
|
+
*/
|
|
195
|
+
export async function install() {
|
|
196
|
+
console.log(c`Installing {<B}${PACKAGE_NAME}{B>}...`)
|
|
197
|
+
console.log()
|
|
198
|
+
|
|
199
|
+
const toInstall = []
|
|
200
|
+
|
|
201
|
+
// Check if main package is already installed
|
|
202
|
+
if(!(await isInstalled(PACKAGE_NAME))) {
|
|
203
|
+
toInstall.push(PACKAGE_NAME)
|
|
204
|
+
} else {
|
|
205
|
+
console.log(c`{F070}✓{/} {<B}${PACKAGE_NAME}{B>} already installed`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Check peer dependencies
|
|
209
|
+
for(const dep of PEER_DEPS) {
|
|
210
|
+
if(!(await isInstalled(dep))) {
|
|
211
|
+
toInstall.push(dep)
|
|
212
|
+
} else {
|
|
213
|
+
console.log(c`{F070}✓{/} {<B}${dep}{B>} already installed`)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Install missing packages
|
|
218
|
+
if(toInstall.length > 0) {
|
|
219
|
+
console.log(c`\n{F027} Installing:{/} ${toInstall.map(p => c`{F172}${p}{/}`).join(", ")}`)
|
|
220
|
+
|
|
221
|
+
const {manager, installCmd} = await getPackageManagerInfo()
|
|
222
|
+
const fullCmd = `${installCmd} ${toInstall.join(" ")}`
|
|
223
|
+
|
|
224
|
+
console.log(c`{F244}Using package manager: ${manager}{/}`)
|
|
225
|
+
console.log(c`{F244}Running: ${fullCmd}{/}`)
|
|
226
|
+
exec(fullCmd)
|
|
227
|
+
|
|
228
|
+
console.log()
|
|
229
|
+
console.log(c`{F070}✓{/} Installation successful.`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log()
|
|
233
|
+
console.log(c`{F039}For detailed setup and configuration options, visit:{/}`)
|
|
234
|
+
console.log(c`https://github.com/gesslar/uglier#readme`)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Generate eslint.config.js file
|
|
239
|
+
*
|
|
240
|
+
* @param {string[]} targets - Target environments (node, web, react, etc.)
|
|
241
|
+
* @returns {Promise<boolean>} True if successful
|
|
242
|
+
*/
|
|
243
|
+
export async function generateConfig(targets = []) {
|
|
244
|
+
const cwd = new CappedDirectoryObject(process.cwd())
|
|
245
|
+
const configFile = new FileObject("eslint.config.js", cwd)
|
|
246
|
+
|
|
247
|
+
if(await configFile.exists) {
|
|
248
|
+
console.log(c`{F214}Error:{/} {<B}eslint.config.js{B>} already exists`)
|
|
249
|
+
console.log(c`Use {<B}npx @gesslar/uglier add <targets>{B>} to add config blocks to it`)
|
|
250
|
+
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Get available configs dynamically
|
|
255
|
+
const configs = await getAvailableConfigs()
|
|
256
|
+
const environmentTargets = configs
|
|
257
|
+
? configs.filter(c => !c.name.startsWith("lints-") && c.name !== "languageOptions" && !c.name.endsWith("-override"))
|
|
258
|
+
.map(c => c.name)
|
|
259
|
+
: ["node", "web", "react", "tauri", "vscode-extension"]
|
|
260
|
+
|
|
261
|
+
// If no targets specified, show error with available options
|
|
262
|
+
if(targets.length === 0) {
|
|
263
|
+
console.log(c`{F214}Error:{/} No targets specified`)
|
|
264
|
+
console.log()
|
|
265
|
+
console.log(c`Available targets: ${environmentTargets.map(t => c`{F172}${t}{/}`).join(", ")}`)
|
|
266
|
+
console.log()
|
|
267
|
+
console.log(c`{F244}Example: npx @gesslar/uglier init ${environmentTargets[0] || "node"}{/}`)
|
|
268
|
+
|
|
269
|
+
return false
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Validate targets
|
|
273
|
+
const validTargets = environmentTargets
|
|
274
|
+
const invalidTargets = targets.filter(t => !validTargets.includes(t))
|
|
275
|
+
|
|
276
|
+
if(invalidTargets.length > 0) {
|
|
277
|
+
console.log(c`{F214}Error:{/} Invalid targets: {F172}${invalidTargets.join(", ")}{/}`)
|
|
278
|
+
console.log(c`Valid targets: {F070}${validTargets.join(", ")}{/}`)
|
|
279
|
+
|
|
280
|
+
return false
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Build the config with comments
|
|
284
|
+
const withArray = ["lints-js", "lints-jsdoc", ...targets]
|
|
285
|
+
|
|
286
|
+
// Get file patterns dynamically from source
|
|
287
|
+
const allConfigs = await getAvailableConfigs()
|
|
288
|
+
const filePatterns = {}
|
|
289
|
+
|
|
290
|
+
if(allConfigs) {
|
|
291
|
+
for(const config of allConfigs) {
|
|
292
|
+
filePatterns[config.name] = config.files
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Build the with array with comments
|
|
297
|
+
const withLines = withArray.map(target => {
|
|
298
|
+
const pattern = filePatterns[target] || "[]"
|
|
299
|
+
|
|
300
|
+
return ` "${target}", // default files: ${pattern}`
|
|
301
|
+
}).join("\n")
|
|
302
|
+
|
|
303
|
+
const configContent = `import uglify from "@gesslar/uglier"
|
|
304
|
+
|
|
305
|
+
export default [
|
|
306
|
+
...uglify({
|
|
307
|
+
with: [
|
|
308
|
+
${withLines}
|
|
309
|
+
]
|
|
310
|
+
})
|
|
311
|
+
]
|
|
312
|
+
`
|
|
313
|
+
|
|
314
|
+
await configFile.write(configContent)
|
|
315
|
+
|
|
316
|
+
console.log(c`{F070}✓{/} Created {<B}eslint.config.js{B>}`)
|
|
317
|
+
console.log()
|
|
318
|
+
console.log(c`{F039}Configuration includes:{/}`)
|
|
319
|
+
|
|
320
|
+
for(const target of withArray) {
|
|
321
|
+
console.log(c` {F070}•{/} ${target}`)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const eslintCmd = await getEslintCommand()
|
|
325
|
+
|
|
326
|
+
console.log()
|
|
327
|
+
console.log(c`{F244}Run {<B}${eslintCmd}{B>} to lint your project{/}`)
|
|
328
|
+
|
|
329
|
+
return true
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Add config blocks to existing eslint.config.js
|
|
334
|
+
*
|
|
335
|
+
* @param {string[]} targets - Target environments to add (node, web, react, etc.)
|
|
336
|
+
* @returns {Promise<boolean>} True if successful
|
|
337
|
+
*/
|
|
338
|
+
export async function addToConfig(targets = []) {
|
|
339
|
+
const cwd = new CappedDirectoryObject(process.cwd())
|
|
340
|
+
const configFile = new FileObject("eslint.config.js", cwd)
|
|
341
|
+
|
|
342
|
+
if(!(await configFile.exists)) {
|
|
343
|
+
console.log(c`{F214}Error:{/} {<B}eslint.config.js{B>} not found`)
|
|
344
|
+
console.log(c`Use {<B}npx @gesslar/uglier init <targets>{B>} to create one first`)
|
|
345
|
+
|
|
346
|
+
return false
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Get available configs dynamically
|
|
350
|
+
const configs = await getAvailableConfigs()
|
|
351
|
+
const environmentTargets = configs
|
|
352
|
+
? configs.filter(c => !c.name.startsWith("lints-") && c.name !== "languageOptions" && !c.name.endsWith("-override"))
|
|
353
|
+
.map(c => c.name)
|
|
354
|
+
: ["node", "web", "react", "tauri", "vscode-extension"]
|
|
355
|
+
|
|
356
|
+
// If no targets specified, show error with available options
|
|
357
|
+
if(targets.length === 0) {
|
|
358
|
+
console.log(c`{F214}Error:{/} No targets specified`)
|
|
359
|
+
console.log()
|
|
360
|
+
console.log(c`Available targets: ${environmentTargets.map(t => c`{F172}${t}{/}`).join(", ")}`)
|
|
361
|
+
console.log()
|
|
362
|
+
console.log(c`{F244}Example: npx @gesslar/uglier add react{/}`)
|
|
363
|
+
|
|
364
|
+
return false
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Validate targets
|
|
368
|
+
const validTargets = environmentTargets
|
|
369
|
+
const invalidTargets = targets.filter(t => !validTargets.includes(t))
|
|
370
|
+
|
|
371
|
+
if(invalidTargets.length > 0) {
|
|
372
|
+
console.log(c`{F214}Error:{/} Invalid targets: {F172}${invalidTargets.join(", ")}{/}`)
|
|
373
|
+
console.log(c`Valid targets: {F070}${validTargets.join(", ")}{/}`)
|
|
374
|
+
|
|
375
|
+
return false
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Read existing config
|
|
379
|
+
const existingContent = await configFile.read()
|
|
380
|
+
|
|
381
|
+
// Parse the with array from the existing config
|
|
382
|
+
const withMatch = existingContent.match(/with:\s*\[([\s\S]*?)\]/m)
|
|
383
|
+
const existingTargets = parseTargetsFromConfig(existingContent)
|
|
384
|
+
|
|
385
|
+
if(existingTargets.length === 0 || !withMatch) {
|
|
386
|
+
console.log(c`{F214}Error:{/} Could not parse existing config`)
|
|
387
|
+
console.log(c`The config file may have a non-standard format`)
|
|
388
|
+
|
|
389
|
+
return false
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Find which targets are new
|
|
393
|
+
const newTargets = targets.filter(t => !existingTargets.includes(t))
|
|
394
|
+
|
|
395
|
+
if(newTargets.length === 0) {
|
|
396
|
+
console.log(c`{F214}Warning:{/} All specified targets already exist in config`)
|
|
397
|
+
console.log()
|
|
398
|
+
console.log(c`Current targets: ${existingTargets.map(t => c`{F070}${t}{/}`).join(", ")}`)
|
|
399
|
+
|
|
400
|
+
return false
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Get file patterns for new targets
|
|
404
|
+
const allConfigs = await getAvailableConfigs()
|
|
405
|
+
const filePatterns = {}
|
|
406
|
+
|
|
407
|
+
if(allConfigs) {
|
|
408
|
+
for(const config of allConfigs) {
|
|
409
|
+
filePatterns[config.name] = config.files
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Build new lines to add
|
|
414
|
+
const newLines = newTargets.map(target => {
|
|
415
|
+
const pattern = filePatterns[target] || "[]"
|
|
416
|
+
|
|
417
|
+
return ` "${target}", // default files: ${pattern}`
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
// Find the position to insert (before the closing bracket)
|
|
421
|
+
const withArrayContent = withMatch[1]
|
|
422
|
+
const lastCommaPos = withArrayContent.lastIndexOf(",")
|
|
423
|
+
|
|
424
|
+
// Build the replacement with new targets added
|
|
425
|
+
let newWithContent
|
|
426
|
+
|
|
427
|
+
if(lastCommaPos === -1) {
|
|
428
|
+
// No existing targets or only one without trailing comma
|
|
429
|
+
newWithContent = withArrayContent.trim() + ",\n" + newLines.join("\n")
|
|
430
|
+
} else {
|
|
431
|
+
newWithContent = withArrayContent + "\n" + newLines.join("\n")
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const newContent = existingContent.replace(
|
|
435
|
+
/with:\s*\[([\s\S]*?)\]/m,
|
|
436
|
+
`with: [\n${newWithContent}\n ]`
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
await configFile.write(newContent)
|
|
440
|
+
|
|
441
|
+
console.log(c`{F070}✓{/} Added config blocks to {<B}eslint.config.js{B>}`)
|
|
442
|
+
console.log()
|
|
443
|
+
console.log(c`{F039}Added targets:{/}`)
|
|
444
|
+
|
|
445
|
+
for(const target of newTargets) {
|
|
446
|
+
console.log(c` {F070}•{/} ${target}`)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const eslintCmd = await getEslintCommand()
|
|
450
|
+
|
|
451
|
+
console.log()
|
|
452
|
+
console.log(c`{F244}Run {<B}${eslintCmd}{B>} to lint your project{/}`)
|
|
453
|
+
|
|
454
|
+
return true
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Remove config blocks from existing eslint.config.js
|
|
459
|
+
*
|
|
460
|
+
* @param {string[]} targets - Target environments to remove (node, web, react, etc.)
|
|
461
|
+
* @returns {Promise<{success: boolean, removedTargets: string[], removedOverrides: string[]}>} Result info
|
|
462
|
+
*/
|
|
463
|
+
export async function removeFromConfig(targets = []) {
|
|
464
|
+
const cwd = new CappedDirectoryObject(process.cwd())
|
|
465
|
+
const configFile = new FileObject("eslint.config.js", cwd)
|
|
466
|
+
|
|
467
|
+
if(!(await configFile.exists)) {
|
|
468
|
+
console.log(c`{F214}Error:{/} {<B}eslint.config.js{B>} not found`)
|
|
469
|
+
console.log(c`Use {<B}npx @gesslar/uglier init <targets>{B>} to create one first`)
|
|
470
|
+
|
|
471
|
+
return {success: false, removedTargets: [], removedOverrides: []}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Get available configs dynamically
|
|
475
|
+
const configs = await getAvailableConfigs()
|
|
476
|
+
const environmentTargets = configs
|
|
477
|
+
? configs.filter(c => !c.name.startsWith("lints-") && c.name !== "languageOptions" && !c.name.endsWith("-override"))
|
|
478
|
+
.map(c => c.name)
|
|
479
|
+
: ["node", "web", "react", "tauri", "vscode-extension"]
|
|
480
|
+
|
|
481
|
+
// If no targets specified, show error with available options
|
|
482
|
+
if(targets.length === 0) {
|
|
483
|
+
console.log(c`{F214}Error:{/} No targets specified`)
|
|
484
|
+
console.log()
|
|
485
|
+
console.log(c`Available targets: ${environmentTargets.map(t => c`{F172}${t}{/}`).join(", ")}`)
|
|
486
|
+
console.log()
|
|
487
|
+
console.log(c`{F244}Example: npx @gesslar/uglier remove react{/}`)
|
|
488
|
+
|
|
489
|
+
return {success: false, removedTargets: [], removedOverrides: []}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Read existing config
|
|
493
|
+
const existingContent = await configFile.read()
|
|
494
|
+
|
|
495
|
+
// Parse the with array from the existing config
|
|
496
|
+
const existingTargets = parseTargetsFromConfig(existingContent)
|
|
497
|
+
|
|
498
|
+
if(existingTargets.length === 0) {
|
|
499
|
+
console.log(c`{F214}Error:{/} Could not parse existing config`)
|
|
500
|
+
console.log(c`The config file may have a non-standard format`)
|
|
501
|
+
|
|
502
|
+
return {success: false, removedTargets: [], removedOverrides: []}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Find which targets exist and can be removed
|
|
506
|
+
const targetsToRemove = targets.filter(t => existingTargets.includes(t))
|
|
507
|
+
const notFoundTargets = targets.filter(t => !existingTargets.includes(t))
|
|
508
|
+
|
|
509
|
+
if(notFoundTargets.length > 0) {
|
|
510
|
+
console.log(c`{F214}Warning:{/} These targets are not in the config: {F172}${notFoundTargets.join(", ")}{/}`)
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if(targetsToRemove.length === 0) {
|
|
514
|
+
console.log(c`{F214}Error:{/} None of the specified targets exist in config`)
|
|
515
|
+
console.log()
|
|
516
|
+
console.log(c`Current targets: ${existingTargets.map(t => c`{F070}${t}{/}`).join(", ")}`)
|
|
517
|
+
|
|
518
|
+
return {success: false, removedTargets: [], removedOverrides: []}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Build new with array content without the removed targets
|
|
522
|
+
const remainingTargets = existingTargets.filter(
|
|
523
|
+
t => !targetsToRemove.includes(t)
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
if(remainingTargets.length === 0) {
|
|
527
|
+
console.log(c`{F214}Error:{/} Cannot remove all targets from config`)
|
|
528
|
+
console.log(c`At least one target must remain`)
|
|
529
|
+
|
|
530
|
+
return {success: false, removedTargets: [], removedOverrides: []}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Get file patterns for remaining targets
|
|
534
|
+
const allConfigs = await getAvailableConfigs()
|
|
535
|
+
const filePatterns = {}
|
|
536
|
+
|
|
537
|
+
if(allConfigs) {
|
|
538
|
+
for(const config of allConfigs) {
|
|
539
|
+
filePatterns[config.name] = config.files
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Build new lines for remaining targets
|
|
544
|
+
const newLines = remainingTargets.map(target => {
|
|
545
|
+
const pattern = filePatterns[target] || "[]"
|
|
546
|
+
|
|
547
|
+
return ` "${target}", // default files: ${pattern}`
|
|
548
|
+
})
|
|
549
|
+
|
|
550
|
+
let newContent = existingContent.replace(
|
|
551
|
+
/with:\s*\[([\s\S]*?)\n\s*\]/m,
|
|
552
|
+
`with: [\n${newLines.join("\n")}\n ]`
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
// Check for and remove overrides for removed targets
|
|
556
|
+
const removedOverrides = []
|
|
557
|
+
const overridesMatch = existingContent.match(/overrides:\s*\{([\s\S]*?)\n\s*\}/m)
|
|
558
|
+
|
|
559
|
+
if(overridesMatch) {
|
|
560
|
+
for(const target of targetsToRemove) {
|
|
561
|
+
// Match override block for this target
|
|
562
|
+
const overridePattern = new RegExp(`\\s*["']${target}["']:\\s*\\{[^}]*\\},?`, "g")
|
|
563
|
+
|
|
564
|
+
if(overridePattern.test(newContent)) {
|
|
565
|
+
removedOverrides.push(target)
|
|
566
|
+
newContent = newContent.replace(overridePattern, "")
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Clean up empty overrides object or trailing commas
|
|
571
|
+
newContent = newContent.replace(/overrides:\s*\{\s*,?\s*\}/m, "")
|
|
572
|
+
newContent = newContent.replace(/,(\s*)\}/g, "$1}")
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
await configFile.write(newContent)
|
|
576
|
+
|
|
577
|
+
console.log(c`{F070}✓{/} Removed config blocks from {<B}eslint.config.js{B>}`)
|
|
578
|
+
console.log()
|
|
579
|
+
console.log(c`{F039}Removed targets:{/}`)
|
|
580
|
+
|
|
581
|
+
for(const target of targetsToRemove) {
|
|
582
|
+
console.log(c` {F070}•{/} ${target}`)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if(removedOverrides.length > 0) {
|
|
586
|
+
console.log()
|
|
587
|
+
console.log(c`{F039}Also removed overrides for:{/}`)
|
|
588
|
+
|
|
589
|
+
for(const target of removedOverrides) {
|
|
590
|
+
console.log(c` {F070}•{/} ${target}`)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const eslintCmd = await getEslintCommand()
|
|
595
|
+
|
|
596
|
+
console.log()
|
|
597
|
+
console.log(c`{F244}Run {<B}${eslintCmd}{B>} to lint your project{/}`)
|
|
598
|
+
|
|
599
|
+
return {success: true, removedTargets: targetsToRemove, removedOverrides}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Show help information
|
|
604
|
+
*/
|
|
605
|
+
export async function showHelp() {
|
|
606
|
+
console.log(c`{F027}@gesslar/uglier{/} - Composable ESLint flat config`)
|
|
607
|
+
|
|
608
|
+
console.log()
|
|
609
|
+
console.log("Usage:")
|
|
610
|
+
console.log()
|
|
611
|
+
console.log(c` {<B}npx @gesslar/uglier install{B>} Install package and dependencies`)
|
|
612
|
+
console.log(c` {<B}npx @gesslar/uglier init <targets>{B>} Generate eslint.config.js with targets`)
|
|
613
|
+
console.log(c` {<B}npx @gesslar/uglier add <targets>{B>} Add config blocks to existing eslint.config.js`)
|
|
614
|
+
console.log(c` {<B}npx @gesslar/uglier remove <targets>{B>} Remove config blocks from existing eslint.config.js`)
|
|
615
|
+
console.log(c` {<B}npx @gesslar/uglier --help{B>} Show this help`)
|
|
616
|
+
console.log()
|
|
617
|
+
|
|
618
|
+
const configs = await getAvailableConfigs()
|
|
619
|
+
|
|
620
|
+
if(configs && configs.length > 0) {
|
|
621
|
+
console.log(c`Available config blocks:`)
|
|
622
|
+
console.log()
|
|
623
|
+
|
|
624
|
+
for(const {name, description} of configs) {
|
|
625
|
+
console.log(c` {<B}${name.padEnd(20)}{B>} ${description}`)
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
console.log("Install the package to see available config blocks.\n")
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
console.log()
|
|
632
|
+
console.log(`Documentation at https://github.com/gesslar/uglier.`)
|
|
633
|
+
}
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"email": "bmw@gesslar.dev",
|
|
7
7
|
"url": "https://gesslar.dev"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.6.0",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
12
|
"url": "git+https://github.com/gesslar/uglier.git"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"main": "src/uglier.js",
|
|
16
16
|
"exports": "./src/uglier.js",
|
|
17
17
|
"bin": {
|
|
18
|
-
"uglier": "./
|
|
18
|
+
"uglier": "./src/install.js"
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
21
|
"src",
|
|
@@ -42,18 +42,19 @@
|
|
|
42
42
|
"node": ">=22"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@gesslar/colours": "^0.
|
|
46
|
-
"@gesslar/toolkit": "^3.
|
|
45
|
+
"@gesslar/colours": "^0.7.1",
|
|
46
|
+
"@gesslar/toolkit": "^3.9.0",
|
|
47
47
|
"@skarab/detect-package-manager": "^1.0.0",
|
|
48
48
|
"@stylistic/eslint-plugin": "^5.6.1",
|
|
49
49
|
"eslint-plugin-jsdoc": "^61.5.0",
|
|
50
|
-
"globals": "^
|
|
50
|
+
"globals": "^17.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@gesslar/uglier": "^0.
|
|
53
|
+
"@gesslar/uglier": "^0.5.1",
|
|
54
54
|
"eslint": "^9.39.2"
|
|
55
55
|
},
|
|
56
56
|
"scripts": {
|
|
57
|
+
"test": "node --test tests/unit/*.test.js",
|
|
57
58
|
"lint": "eslint",
|
|
58
59
|
"lint:fix": "eslint --fix",
|
|
59
60
|
"update": "pnpm up --latest --recursive",
|
package/src/install.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file install.js - Auto-installer and config generator for @gesslar/uglier
|
|
5
|
+
*
|
|
6
|
+
* @description
|
|
7
|
+
* This script can be run via `npx @gesslar/uglier` to automatically install
|
|
8
|
+
* the package and its peer dependencies to the current project.
|
|
9
|
+
*
|
|
10
|
+
* Commands:
|
|
11
|
+
* - npx @gesslar/uglier install - Install package and dependencies
|
|
12
|
+
* - npx @gesslar/uglier init <targets> - Generate eslint.config.js with targets
|
|
13
|
+
* - npx @gesslar/uglier add <targets> - Add config blocks to existing eslint.config.js
|
|
14
|
+
* - npx @gesslar/uglier remove <targets> - Remove config blocks from existing eslint.config.js
|
|
15
|
+
* - npx @gesslar/uglier --help - Show help
|
|
16
|
+
*
|
|
17
|
+
* Installation does:
|
|
18
|
+
* 1. Install @gesslar/uglier as a dev dependency
|
|
19
|
+
* 2. Install eslint as a peer dependency (if not present)
|
|
20
|
+
*
|
|
21
|
+
* Note: All other dependencies (@stylistic/eslint-plugin, eslint-plugin-jsdoc, globals)
|
|
22
|
+
* are bundled with the package and don't need to be installed separately.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import c from "@gesslar/colours"
|
|
26
|
+
import {
|
|
27
|
+
install,
|
|
28
|
+
generateConfig,
|
|
29
|
+
addToConfig,
|
|
30
|
+
removeFromConfig,
|
|
31
|
+
showHelp
|
|
32
|
+
} from "../bin/cli.js"
|
|
33
|
+
|
|
34
|
+
// Parse command line arguments and run
|
|
35
|
+
const args = process.argv.slice(2)
|
|
36
|
+
|
|
37
|
+
if(args.includes("--help") || args.includes("-h")) {
|
|
38
|
+
await showHelp()
|
|
39
|
+
} else if(args[0] === "install") {
|
|
40
|
+
await install()
|
|
41
|
+
} else if(args[0] === "init") {
|
|
42
|
+
const targets = args.slice(1)
|
|
43
|
+
|
|
44
|
+
await generateConfig(targets)
|
|
45
|
+
} else if(args[0] === "add") {
|
|
46
|
+
const targets = args.slice(1)
|
|
47
|
+
|
|
48
|
+
await addToConfig(targets)
|
|
49
|
+
} else if(args[0] === "remove") {
|
|
50
|
+
const targets = args.slice(1)
|
|
51
|
+
|
|
52
|
+
await removeFromConfig(targets)
|
|
53
|
+
} else {
|
|
54
|
+
// No command or unknown command - show help
|
|
55
|
+
console.log(c`{F214}Error:{/} Unknown command or no command specified`)
|
|
56
|
+
console.log()
|
|
57
|
+
await showHelp()
|
|
58
|
+
}
|
package/bin/install.js
DELETED
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file install.js - Auto-installer and config generator for @gesslar/uglier
|
|
5
|
-
*
|
|
6
|
-
* @description
|
|
7
|
-
* This script can be run via `npx @gesslar/uglier` to automatically install
|
|
8
|
-
* the package and its peer dependencies to the current project.
|
|
9
|
-
*
|
|
10
|
-
* Commands:
|
|
11
|
-
* - npx @gesslar/uglier - Install package and dependencies
|
|
12
|
-
* - npx @gesslar/uglier init - Generate eslint.config.js with prompts
|
|
13
|
-
* - npx @gesslar/uglier init node - Generate eslint.config.js for Node.js
|
|
14
|
-
* - npx @gesslar/uglier --help - Show help
|
|
15
|
-
*
|
|
16
|
-
* Installation does:
|
|
17
|
-
* 1. Install @gesslar/uglier as a dev dependency
|
|
18
|
-
* 2. Install eslint as a peer dependency (if not present)
|
|
19
|
-
*
|
|
20
|
-
* Note: All other dependencies (@stylistic/eslint-plugin, eslint-plugin-jsdoc, globals)
|
|
21
|
-
* are bundled with the package and don't need to be installed separately.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import {execSync} from "child_process"
|
|
25
|
-
import {dirname} from "path"
|
|
26
|
-
import {fileURLToPath} from "url"
|
|
27
|
-
import {FileObject, DirectoryObject} from "@gesslar/toolkit"
|
|
28
|
-
import c from "@gesslar/colours"
|
|
29
|
-
import {detectAgent} from "@skarab/detect-package-manager"
|
|
30
|
-
|
|
31
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
32
|
-
const __dirname = dirname(__filename)
|
|
33
|
-
|
|
34
|
-
const PACKAGE_NAME = "@gesslar/uglier"
|
|
35
|
-
|
|
36
|
-
// Only peer dependencies need to be installed separately
|
|
37
|
-
// (all other dependencies come bundled with the package)
|
|
38
|
-
const PEER_DEPS = [
|
|
39
|
-
"eslint"
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get install command for detected package manager
|
|
44
|
-
*
|
|
45
|
-
* @returns {Promise<{manager: string, installCmd: string}>} Package manager info
|
|
46
|
-
*/
|
|
47
|
-
async function getPackageManagerInfo() {
|
|
48
|
-
const agent = await detectAgent()
|
|
49
|
-
const manager = agent?.name || "npm"
|
|
50
|
-
|
|
51
|
-
const commands = {
|
|
52
|
-
npm: "npm i -D",
|
|
53
|
-
pnpm: "pnpm i -D",
|
|
54
|
-
yarn: "yarn add -D",
|
|
55
|
-
bun: "bun add -d"
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
manager,
|
|
60
|
-
installCmd: commands[manager] || commands.npm
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Execute a command and return output
|
|
66
|
-
*
|
|
67
|
-
* @param {string} cmd - Command to execute
|
|
68
|
-
* @returns {string} Command output
|
|
69
|
-
*/
|
|
70
|
-
function exec(cmd) {
|
|
71
|
-
try {
|
|
72
|
-
return execSync(cmd, {encoding: "utf8", stdio: "pipe"})
|
|
73
|
-
} catch(error) {
|
|
74
|
-
console.error(`Error executing: ${cmd}`)
|
|
75
|
-
console.error(error.message)
|
|
76
|
-
process.exit(1)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Get available configs from the source file
|
|
82
|
-
*
|
|
83
|
-
* @returns {Promise<Array<{name: string, description: string, files: string}>|null>} Available configs
|
|
84
|
-
*/
|
|
85
|
-
async function getAvailableConfigs() {
|
|
86
|
-
try {
|
|
87
|
-
// Try to read from installed package or local source
|
|
88
|
-
const localDir = new DirectoryObject(`${__dirname}/../src`)
|
|
89
|
-
const installedDir = new DirectoryObject(`${process.cwd()}/node_modules/${PACKAGE_NAME}/src`)
|
|
90
|
-
|
|
91
|
-
const localSource = new FileObject("uglier.js", localDir)
|
|
92
|
-
const installedSource = new FileObject("uglier.js", installedDir)
|
|
93
|
-
|
|
94
|
-
let uglierFile = null
|
|
95
|
-
|
|
96
|
-
if(await localSource.exists) {
|
|
97
|
-
uglierFile = localSource
|
|
98
|
-
} else if(await installedSource.exists) {
|
|
99
|
-
uglierFile = installedSource
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if(!uglierFile) {
|
|
103
|
-
return null
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const source = await uglierFile.read()
|
|
107
|
-
|
|
108
|
-
// Extract config names, descriptions, and default files
|
|
109
|
-
const configs = []
|
|
110
|
-
// Match individual config blocks within CONFIGS object
|
|
111
|
-
const configBlockRegex = /\/\*\*\s*\n\s*\*\s*([^\n@*]+?)\s*\n(?:\s*\*[^\n]*\n)*?\s*\*\/\s*\n\s*["']([^"']+)["']:\s*\([^)]*\)\s*=>\s*\{[^}]*?files\s*=\s*(\[[^\]]+\])/g
|
|
112
|
-
let match
|
|
113
|
-
|
|
114
|
-
while((match = configBlockRegex.exec(source)) !== null) {
|
|
115
|
-
configs.push({
|
|
116
|
-
name: match[2],
|
|
117
|
-
description: match[1].trim(),
|
|
118
|
-
files: match[3]
|
|
119
|
-
})
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return configs
|
|
123
|
-
} catch {
|
|
124
|
-
return null
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Show help information
|
|
130
|
-
*/
|
|
131
|
-
async function showHelp() {
|
|
132
|
-
console.log(c`{F027}@gesslar/uglier{/} - Composable ESLint flat config`)
|
|
133
|
-
|
|
134
|
-
console.log()
|
|
135
|
-
console.log("Usage:")
|
|
136
|
-
console.log()
|
|
137
|
-
console.log(c` {<B}npx @gesslar/uglier{B>} Install package and dependencies`)
|
|
138
|
-
console.log(c` {<B}npx @gesslar/uglier init{B>} Generate eslint.config.js interactively`)
|
|
139
|
-
console.log(c` {<B}npx @gesslar/uglier init <targets>{B>} Generate eslint.config.js with targets`)
|
|
140
|
-
console.log(c` {<B}npx @gesslar/uglier --help{B>} Show this help`)
|
|
141
|
-
console.log()
|
|
142
|
-
|
|
143
|
-
const configs = await getAvailableConfigs()
|
|
144
|
-
|
|
145
|
-
if(configs && configs.length > 0) {
|
|
146
|
-
console.log(c`Available config blocks:`)
|
|
147
|
-
console.log()
|
|
148
|
-
|
|
149
|
-
for(const {name, description} of configs) {
|
|
150
|
-
console.log(c` {<B}${name.padEnd(20)}{B>} ${description}`)
|
|
151
|
-
}
|
|
152
|
-
} else {
|
|
153
|
-
console.log("Install the package to see available config blocks.\n")
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
console.log()
|
|
157
|
-
console.log(`Documentation at https://github.com/gesslar/uglier.`)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Check if a package is already installed
|
|
162
|
-
*
|
|
163
|
-
* @param {string} packageName - Name of package to check
|
|
164
|
-
* @returns {Promise<boolean>} True if installed
|
|
165
|
-
*/
|
|
166
|
-
async function isInstalled(packageName) {
|
|
167
|
-
try {
|
|
168
|
-
const packageJsonFile = new FileObject("package.json", process.cwd())
|
|
169
|
-
|
|
170
|
-
if(!(await packageJsonFile.exists)) {
|
|
171
|
-
console.warn(c`No {<B}package.json{B>} found. Please initialize your project first.`)
|
|
172
|
-
process.exit(1)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const packageJson = await packageJsonFile.loadData("json")
|
|
176
|
-
const allDeps = {
|
|
177
|
-
...packageJson.dependencies,
|
|
178
|
-
...packageJson.devDependencies,
|
|
179
|
-
...packageJson.peerDependencies
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return packageName in allDeps
|
|
183
|
-
} catch {
|
|
184
|
-
return false
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Main installation routine
|
|
190
|
-
*/
|
|
191
|
-
async function install() {
|
|
192
|
-
console.log(c`Installing {<B}${PACKAGE_NAME}{B>}...`)
|
|
193
|
-
console.log()
|
|
194
|
-
|
|
195
|
-
const toInstall = []
|
|
196
|
-
|
|
197
|
-
// Check if main package is already installed
|
|
198
|
-
if(!(await isInstalled(PACKAGE_NAME))) {
|
|
199
|
-
toInstall.push(PACKAGE_NAME)
|
|
200
|
-
} else {
|
|
201
|
-
console.log(c`{F070}✓{/} {<B}${PACKAGE_NAME}{B>} already installed`)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Check peer dependencies
|
|
205
|
-
for(const dep of PEER_DEPS) {
|
|
206
|
-
if(!(await isInstalled(dep))) {
|
|
207
|
-
toInstall.push(dep)
|
|
208
|
-
} else {
|
|
209
|
-
console.log(c`{F070}✓{/} {<B}${dep}{B>} already installed`)
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Install missing packages
|
|
214
|
-
if(toInstall.length > 0) {
|
|
215
|
-
console.log(c`\n{F027} Installing:{/} ${toInstall.map(p => c`{F172}${p}{/}`).join(", ")}`)
|
|
216
|
-
|
|
217
|
-
const {manager, installCmd} = await getPackageManagerInfo()
|
|
218
|
-
const fullCmd = `${installCmd} ${toInstall.join(" ")}`
|
|
219
|
-
|
|
220
|
-
console.log(c`{F244}Using package manager: ${manager}{/}`)
|
|
221
|
-
console.log(c`{F244}Running: ${fullCmd}{/}`)
|
|
222
|
-
exec(fullCmd)
|
|
223
|
-
|
|
224
|
-
console.log()
|
|
225
|
-
console.log(c`{F070}✓{/} Installation successful.`)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
console.log()
|
|
229
|
-
console.log(c`{F039}For detailed setup and configuration options, visit:{/}`)
|
|
230
|
-
console.log(c`https://github.com/gesslar/uglier#readme`)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Generate eslint.config.js file
|
|
235
|
-
*
|
|
236
|
-
* @param {string[]} targets - Target environments (node, web, react, etc.)
|
|
237
|
-
*/
|
|
238
|
-
async function generateConfig(targets = []) {
|
|
239
|
-
const configFile = new FileObject("eslint.config.js", process.cwd())
|
|
240
|
-
|
|
241
|
-
if(await configFile.exists) {
|
|
242
|
-
console.log(c`{F214}Warning:{/} {<B}eslint.config.js{B>} already exists`)
|
|
243
|
-
console.log(c`Delete it first or edit it manually`)
|
|
244
|
-
|
|
245
|
-
return
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Get available configs dynamically
|
|
249
|
-
const configs = await getAvailableConfigs()
|
|
250
|
-
const environmentTargets = configs
|
|
251
|
-
? configs.filter(c => !c.name.startsWith("lints-") && c.name !== "languageOptions" && !c.name.endsWith("-override"))
|
|
252
|
-
.map(c => c.name)
|
|
253
|
-
: ["node", "web", "react", "tauri", "vscode-extension"]
|
|
254
|
-
|
|
255
|
-
// If no targets specified, make it interactive
|
|
256
|
-
if(targets.length === 0) {
|
|
257
|
-
console.log(c`{F027}Choose your target environments:{/}`)
|
|
258
|
-
console.log()
|
|
259
|
-
console.log(c`Available targets: ${environmentTargets.map(t => c`{F172}${t}{/}`).join(", ")}`)
|
|
260
|
-
console.log()
|
|
261
|
-
console.log(c`{F244}Example: npx @gesslar/uglier init ${environmentTargets[0] || "node"}{/}`)
|
|
262
|
-
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Validate targets
|
|
267
|
-
const validTargets = environmentTargets
|
|
268
|
-
const invalidTargets = targets.filter(t => !validTargets.includes(t))
|
|
269
|
-
|
|
270
|
-
if(invalidTargets.length > 0) {
|
|
271
|
-
console.log(c`{F214}Error:{/} Invalid targets: {F172}${invalidTargets.join(", ")}{/}`)
|
|
272
|
-
console.log(c`Valid targets: {F070}${validTargets.join(", ")}{/}`)
|
|
273
|
-
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// Build the config with comments
|
|
278
|
-
const withArray = ["lints-js", "lints-jsdoc", ...targets]
|
|
279
|
-
|
|
280
|
-
// Get file patterns dynamically from source
|
|
281
|
-
const allConfigs = await getAvailableConfigs()
|
|
282
|
-
const filePatterns = {}
|
|
283
|
-
|
|
284
|
-
if(allConfigs) {
|
|
285
|
-
for(const config of allConfigs) {
|
|
286
|
-
filePatterns[config.name] = config.files
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Build the with array with comments
|
|
291
|
-
const withLines = withArray.map(target => {
|
|
292
|
-
const pattern = filePatterns[target] || "[]"
|
|
293
|
-
|
|
294
|
-
return ` "${target}", // default files: ${pattern}`
|
|
295
|
-
}).join("\n")
|
|
296
|
-
|
|
297
|
-
const configContent = `import uglify from "@gesslar/uglier"
|
|
298
|
-
|
|
299
|
-
export default [
|
|
300
|
-
...uglify({
|
|
301
|
-
with: [
|
|
302
|
-
${withLines}
|
|
303
|
-
]
|
|
304
|
-
})
|
|
305
|
-
]
|
|
306
|
-
`
|
|
307
|
-
|
|
308
|
-
await configFile.write(configContent)
|
|
309
|
-
|
|
310
|
-
console.log(c`{F070}✓{/} Created {<B}eslint.config.js{B>}`)
|
|
311
|
-
console.log()
|
|
312
|
-
console.log(c`{F039}Configuration includes:{/}`)
|
|
313
|
-
|
|
314
|
-
for(const target of withArray) {
|
|
315
|
-
console.log(c` {F070}•{/} ${target}`)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
console.log()
|
|
319
|
-
console.log(c`{F244}Run {<B}npx eslint .{B>} to lint your project{/}`)
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Parse command line arguments and run
|
|
323
|
-
const args = process.argv.slice(2)
|
|
324
|
-
|
|
325
|
-
if(args.includes("--help") || args.includes("-h")) {
|
|
326
|
-
await showHelp()
|
|
327
|
-
} else if(args[0] === "init") {
|
|
328
|
-
const targets = args.slice(1)
|
|
329
|
-
|
|
330
|
-
await install()
|
|
331
|
-
await generateConfig(targets)
|
|
332
|
-
} else {
|
|
333
|
-
await install()
|
|
334
|
-
}
|