@luxkit/cli 1.1.1 → 1.1.2
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 +24 -76
- package/dist/index.js +305 -299
- package/dist/skills/lux/references/custom-preset-setting.md +67 -0
- package/dist/skills/lux/skill.md +12 -2
- package/package.json +3 -10
package/README.md
CHANGED
|
@@ -18,48 +18,29 @@
|
|
|
18
18
|
|
|
19
19
|
### What is lux?
|
|
20
20
|
|
|
21
|
-
`lux` is a CLI tool that
|
|
21
|
+
`lux` is a CLI tool that sets up project lint configs and VSCode workspace settings with a single command — saving you from repetitive configuration overhead and wasted tokens. It generates ESLint, Prettier, CSpell, Stylelint,EditorConfig configs and VSCode settings from presets — with smart merge and conflict resolution. Configurations can be customized to your needs.
|
|
22
22
|
|
|
23
23
|
<div align="center">
|
|
24
24
|
<img src="https://github.com/TTT1231/lux/blob/main/demo.gif?raw=true" alt="lux demo" width="640" />
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
|
-
### ✨ Key Highlights
|
|
28
|
-
|
|
29
|
-
| Feature | Description |
|
|
30
|
-
| :------------------------- | :--------------------------------------------------------------------------------------------------- |
|
|
31
|
-
| 🎯 **One Command Setup** | `lux fmt web-vue` generates all linting & formatting configs instantly |
|
|
32
|
-
| 🔧 **6 Fmt Presets** | `web-vue` · `web-react` · `electron-vue` · `uniapp` · `node` · `nest` — each with curated rules |
|
|
33
|
-
| 🖥️ **7 VSCode Presets** | `web-vue` · `web-react` · `electron-vue` · `uniapp` · `node` · `nest` · `go` — settings + extensions |
|
|
34
|
-
| 🔀 **Smart Merge** | Preset wins for linting keys; user wins for personal preferences |
|
|
35
|
-
| 🛡️ **Conflict Resolution** | `neverOverwrite` / `forceOverwrite` lists + `--force` flag |
|
|
36
|
-
| 📦 **Auto Install** | Detects bun / pnpm / yarn / npm and installs devDependencies |
|
|
37
|
-
| 🔍 **Fuzzy Matching** | Typo a preset name? Levenshtein distance finds the closest match |
|
|
38
|
-
| 🧪 **Dry Run** | Preview all changes with `--dry-run` before writing anything |
|
|
39
|
-
| 🔗 **Script Injection** | Auto-injects `<pm> lint` / `<pm> format` scripts into package.json |
|
|
40
|
-
| 🌐 **Proxy Management** | Persistent proxy config with `set` / `unset` — copy to CMD / PowerShell / Bash |
|
|
41
|
-
| 🔄 **Self-Update** | `lux update` checks and installs the latest version automatically |
|
|
42
|
-
|
|
43
|
-
<br />
|
|
44
|
-
|
|
45
27
|
### Quick Start
|
|
46
28
|
|
|
47
29
|
```bash
|
|
48
30
|
# Install globally (pick your package manager)
|
|
49
31
|
npm install -g @luxkit/cli
|
|
50
|
-
# or
|
|
51
|
-
bun add -g @luxkit/cli
|
|
52
32
|
|
|
53
|
-
# Initialize
|
|
54
|
-
lux
|
|
33
|
+
# Initialize skill and preset
|
|
34
|
+
lux init && lux init --preset
|
|
35
|
+
|
|
36
|
+
# Lint usage
|
|
37
|
+
lux fmt web-vue # Configure web-vue lint — ESLint, Prettier, CSpell
|
|
55
38
|
lux fmt web-vue --stylelint # Also include Stylelint
|
|
56
39
|
lux fmt web-vue --editorconfig # Also include EditorConfig
|
|
57
40
|
|
|
58
|
-
#
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# Initialize AI coding tool skills
|
|
62
|
-
lux init # Select tool interactively, copy skills to project
|
|
41
|
+
# VSCode config (optional)
|
|
42
|
+
# If you've already configured globally, you can skip this
|
|
43
|
+
lux vscode web-vue # Generate .vscode/settings.json + extensions.json (per-project)
|
|
63
44
|
|
|
64
45
|
# List available presets
|
|
65
46
|
lux fmt list
|
|
@@ -68,15 +49,26 @@ lux vscode list
|
|
|
68
49
|
|
|
69
50
|
<br />
|
|
70
51
|
|
|
52
|
+
### Custom Configuration
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Check if skill and presets are initialized (skip if already done)
|
|
56
|
+
lux init && lux init --preset
|
|
57
|
+
|
|
58
|
+
# Use an agent to customize a preset
|
|
59
|
+
/lux help me configure my web react lint template to fit my development project style
|
|
60
|
+
```
|
|
61
|
+
|
|
71
62
|
### CLI Commands
|
|
72
63
|
|
|
73
64
|
| Command | Description |
|
|
74
65
|
| :-------------------------- | :---------------------------------------------------------------- |
|
|
75
|
-
| `lux fmt <preset>` | Initialize
|
|
76
|
-
| `lux fmt list` | List available
|
|
77
|
-
| `lux vscode <preset>` |
|
|
66
|
+
| `lux fmt <preset>` | Initialize lint configs |
|
|
67
|
+
| `lux fmt list` | List available lint presets |
|
|
68
|
+
| `lux vscode <preset>` | Configure VSCode settings (per-project) |
|
|
78
69
|
| `lux vscode list` | List available VSCode presets |
|
|
79
|
-
| `lux init` | Initialize
|
|
70
|
+
| `lux init` | Initialize skills |
|
|
71
|
+
| `lux init --preset` | Initialize all presets |
|
|
80
72
|
| `lux set <key=value> [...]` | Persist proxy env vars (e.g. `https_proxy=http://127.0.0.1:7890`) |
|
|
81
73
|
| `lux unset` | Clear all stored proxy configuration |
|
|
82
74
|
| `lux show env` | Display stored proxy environment variables |
|
|
@@ -88,19 +80,6 @@ lux vscode list
|
|
|
88
80
|
|
|
89
81
|
<br />
|
|
90
82
|
|
|
91
|
-
### Available Presets
|
|
92
|
-
|
|
93
|
-
| Preset | Fmt | VSCode | Stack |
|
|
94
|
-
| :------------- | :-: | :----: | :------------------------- |
|
|
95
|
-
| `web-vue` | ✅ | ✅ | Vue 3 / Vite / TS / CSS |
|
|
96
|
-
| `web-react` | ✅ | ✅ | React / Vite / TS / CSS |
|
|
97
|
-
| `electron-vue` | ✅ | ✅ | Electron + Vue / Web stack |
|
|
98
|
-
| `node` | ✅ | ✅ | Node.js backend |
|
|
99
|
-
| `nest` | ✅ | ✅ | NestJS backend |
|
|
100
|
-
| `go` | — | ✅ | Go backend |
|
|
101
|
-
|
|
102
|
-
<br />
|
|
103
|
-
|
|
104
83
|
### Options
|
|
105
84
|
|
|
106
85
|
```bash
|
|
@@ -147,37 +126,6 @@ lux fmt web-vue
|
|
|
147
126
|
|
|
148
127
|
<br />
|
|
149
128
|
|
|
150
|
-
### Tech Stack
|
|
151
|
-
|
|
152
|
-
| Category | Technology |
|
|
153
|
-
| :------- | :----------------------------------------- |
|
|
154
|
-
| Language | TypeScript 6.0 (ESM-only) |
|
|
155
|
-
| Runtime | Node.js 18+ |
|
|
156
|
-
| Build | tsup |
|
|
157
|
-
| Test | Vitest (unit + acceptance) |
|
|
158
|
-
| CLI | Commander.js |
|
|
159
|
-
| Output | Chalk |
|
|
160
|
-
| Bundle | Minimal runtime deps (chalk + commander + @clack/prompts) |
|
|
161
|
-
|
|
162
|
-
<br />
|
|
163
|
-
|
|
164
|
-
### Development
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
git clone git@github.com:TTT1231/lux.git
|
|
168
|
-
cd lux
|
|
169
|
-
bun install
|
|
170
|
-
|
|
171
|
-
bun link # Register `lux` globally for testing
|
|
172
|
-
lux fmt web-vue # Test it on any project
|
|
173
|
-
|
|
174
|
-
bun test # Run tests
|
|
175
|
-
bun build # Build to dist/
|
|
176
|
-
bun code:check:all # lint + format + spell check
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
<br />
|
|
180
|
-
|
|
181
129
|
### 🤝 Support
|
|
182
130
|
|
|
183
131
|
If you have any questions or run into issues, feel free to [open an issue](https://github.com/TTT1231/lux/issues) on GitHub.
|
package/dist/index.js
CHANGED
|
@@ -12,13 +12,13 @@ var electronVueFmt = {
|
|
|
12
12
|
name: "electron-vue",
|
|
13
13
|
description: "Vue 3 + Electron desktop app",
|
|
14
14
|
eslint: () => `import withVue from '@vue/eslint-config-typescript'
|
|
15
|
-
import
|
|
15
|
+
import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
|
|
16
16
|
import pluginVue from 'eslint-plugin-vue'
|
|
17
17
|
|
|
18
18
|
export default [
|
|
19
19
|
...pluginVue.configs['flat/recommended'],
|
|
20
20
|
...withVue(),
|
|
21
|
-
|
|
21
|
+
prettierConfig,
|
|
22
22
|
{
|
|
23
23
|
rules: {
|
|
24
24
|
'vue/multi-word-component-names': 'off',
|
|
@@ -79,7 +79,8 @@ out/
|
|
|
79
79
|
version: "0.2",
|
|
80
80
|
language: "en,en-US",
|
|
81
81
|
allowCompoundWords: true,
|
|
82
|
-
words: ["vite", "pinia", "vueuse", "unplugin", "electron", "electron-builder"]
|
|
82
|
+
words: ["vite", "pinia", "vueuse", "unplugin", "electron", "electron-builder"],
|
|
83
|
+
ignorePaths: ["**/*.svg", "**/*.png"]
|
|
83
84
|
},
|
|
84
85
|
null,
|
|
85
86
|
2
|
|
@@ -115,18 +116,9 @@ trim_trailing_whitespace = false
|
|
|
115
116
|
]
|
|
116
117
|
},
|
|
117
118
|
scripts: {
|
|
118
|
-
lint:
|
|
119
|
-
"lint:fix": 'eslint "src/**/*.{
|
|
120
|
-
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
|
|
121
|
-
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
122
|
-
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
123
|
-
"stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
|
|
124
|
-
cspell: 'cspell --gitignore "src/**/*"',
|
|
125
|
-
"type:check": "vue-tsc --noEmit",
|
|
126
|
-
"code:check": "<pm> lint && <pm> format:check",
|
|
127
|
-
"code:fix": "<pm> lint:fix && <pm> format",
|
|
128
|
-
"code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
|
|
129
|
-
"code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
|
|
119
|
+
lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
|
|
120
|
+
"lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
|
|
121
|
+
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
|
|
130
122
|
}
|
|
131
123
|
};
|
|
132
124
|
|
|
@@ -163,7 +155,8 @@ coverage/
|
|
|
163
155
|
version: "0.2",
|
|
164
156
|
language: "en,en-US",
|
|
165
157
|
allowCompoundWords: true,
|
|
166
|
-
words: ["nestjs", "typeorm", "dtos"]
|
|
158
|
+
words: ["nestjs", "typeorm", "dtos"],
|
|
159
|
+
ignorePaths: ["**/*.svg", "**/*.png"]
|
|
167
160
|
},
|
|
168
161
|
null,
|
|
169
162
|
2
|
|
@@ -184,12 +177,10 @@ trim_trailing_whitespace = false
|
|
|
184
177
|
dependencies: {
|
|
185
178
|
dev: ["prettier", "cspell"]
|
|
186
179
|
},
|
|
187
|
-
// NestJS: only append new scripts, don't conflict with existing ones
|
|
188
180
|
scripts: {
|
|
189
|
-
|
|
190
|
-
"
|
|
191
|
-
|
|
192
|
-
"code:check:all": "<pm> lint && <pm> format:check && <pm> cspell"
|
|
181
|
+
lint: 'eslint "{src,apps,libs,test}/**/*.ts" --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit',
|
|
182
|
+
"lint:fix": 'eslint "{src,apps,libs,test}/**/*.ts" --cache --cache-location node_modules/.cache/eslint --fix',
|
|
183
|
+
format: 'prettier --write "src/**/*.{ts,js,json}"'
|
|
193
184
|
}
|
|
194
185
|
};
|
|
195
186
|
|
|
@@ -268,7 +259,8 @@ coverage/
|
|
|
268
259
|
version: "0.2",
|
|
269
260
|
language: "en,en-US",
|
|
270
261
|
allowCompoundWords: true,
|
|
271
|
-
words: []
|
|
262
|
+
words: [],
|
|
263
|
+
ignorePaths: ["**/*.svg", "**/*.png"]
|
|
272
264
|
},
|
|
273
265
|
null,
|
|
274
266
|
2
|
|
@@ -298,16 +290,9 @@ trim_trailing_whitespace = false
|
|
|
298
290
|
]
|
|
299
291
|
},
|
|
300
292
|
scripts: {
|
|
301
|
-
lint:
|
|
302
|
-
"lint:fix":
|
|
303
|
-
format: 'prettier --write "src/**/*.{ts,js,json}"'
|
|
304
|
-
"format:check": 'prettier --check "src/**/*.{ts,js,json}"',
|
|
305
|
-
cspell: 'cspell --gitignore "src/**/*"',
|
|
306
|
-
"type:check": "tsc --noEmit",
|
|
307
|
-
"code:check": "<pm> lint && <pm> format:check",
|
|
308
|
-
"code:fix": "<pm> lint:fix && <pm> format",
|
|
309
|
-
"code:check:all": "<pm> lint && <pm> format:check && <pm> cspell",
|
|
310
|
-
"code:fix:all": "<pm> lint:fix && <pm> format"
|
|
293
|
+
lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit',
|
|
294
|
+
"lint:fix": "eslint . --cache --cache-location node_modules/.cache/eslint --fix",
|
|
295
|
+
format: 'prettier --write "src/**/*.{ts,js,json}"'
|
|
311
296
|
}
|
|
312
297
|
};
|
|
313
298
|
|
|
@@ -316,13 +301,13 @@ var uniappFmt = {
|
|
|
316
301
|
name: "uniapp",
|
|
317
302
|
description: "Vue 3 + UniApp WeChat mini program",
|
|
318
303
|
eslint: () => `import withVue from '@vue/eslint-config-typescript'
|
|
319
|
-
import
|
|
304
|
+
import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
|
|
320
305
|
import pluginVue from 'eslint-plugin-vue'
|
|
321
306
|
|
|
322
307
|
export default [
|
|
323
308
|
...pluginVue.configs['flat/recommended'],
|
|
324
309
|
...withVue(),
|
|
325
|
-
|
|
310
|
+
prettierConfig,
|
|
326
311
|
{
|
|
327
312
|
rules: {
|
|
328
313
|
'vue/multi-word-component-names': 'off',
|
|
@@ -381,7 +366,8 @@ unpackage/
|
|
|
381
366
|
version: "0.2",
|
|
382
367
|
language: "en,en-US",
|
|
383
368
|
allowCompoundWords: true,
|
|
384
|
-
words: ["vite", "pinia", "vueuse", "unplugin", "uniapp"]
|
|
369
|
+
words: ["vite", "pinia", "vueuse", "unplugin", "uniapp"],
|
|
370
|
+
ignorePaths: ["**/*.svg", "**/*.png"]
|
|
385
371
|
},
|
|
386
372
|
null,
|
|
387
373
|
2
|
|
@@ -417,18 +403,9 @@ trim_trailing_whitespace = false
|
|
|
417
403
|
]
|
|
418
404
|
},
|
|
419
405
|
scripts: {
|
|
420
|
-
lint:
|
|
421
|
-
"lint:fix": 'eslint "src/**/*.{
|
|
422
|
-
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
|
|
423
|
-
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
424
|
-
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
425
|
-
"stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
|
|
426
|
-
cspell: 'cspell --gitignore "src/**/*"',
|
|
427
|
-
"type:check": "vue-tsc --noEmit",
|
|
428
|
-
"code:check": "<pm> lint && <pm> format:check",
|
|
429
|
-
"code:fix": "<pm> lint:fix && <pm> format",
|
|
430
|
-
"code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
|
|
431
|
-
"code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
|
|
406
|
+
lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
|
|
407
|
+
"lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
|
|
408
|
+
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
|
|
432
409
|
}
|
|
433
410
|
};
|
|
434
411
|
|
|
@@ -507,7 +484,8 @@ dist/
|
|
|
507
484
|
version: "0.2",
|
|
508
485
|
language: "en,en-US",
|
|
509
486
|
allowCompoundWords: true,
|
|
510
|
-
words: ["vite", "react", "zustand", "tanstack"]
|
|
487
|
+
words: ["vite", "react", "zustand", "tanstack"],
|
|
488
|
+
ignorePaths: ["**/*.svg", "**/*.png"]
|
|
511
489
|
},
|
|
512
490
|
null,
|
|
513
491
|
2
|
|
@@ -545,18 +523,9 @@ trim_trailing_whitespace = false
|
|
|
545
523
|
]
|
|
546
524
|
},
|
|
547
525
|
scripts: {
|
|
548
|
-
lint:
|
|
549
|
-
"lint:fix": 'eslint "src/**/*.{
|
|
550
|
-
format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"'
|
|
551
|
-
"format:check": 'prettier --check "src/**/*.{ts,js,json,jsx,tsx,css,scss}"',
|
|
552
|
-
stylelint: 'stylelint "src/**/*.{css,scss}"',
|
|
553
|
-
"stylelint:fix": 'stylelint "src/**/*.{css,scss}" --fix',
|
|
554
|
-
cspell: 'cspell --gitignore "src/**/*"',
|
|
555
|
-
"type:check": "tsc --noEmit",
|
|
556
|
-
"code:check": "<pm> lint && <pm> format:check",
|
|
557
|
-
"code:fix": "<pm> lint:fix && <pm> format",
|
|
558
|
-
"code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
|
|
559
|
-
"code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
|
|
526
|
+
lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit && stylelint "src/**/*.{css,scss}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
|
|
527
|
+
"lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
|
|
528
|
+
format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"'
|
|
560
529
|
}
|
|
561
530
|
};
|
|
562
531
|
|
|
@@ -565,13 +534,13 @@ var webVueFmt = {
|
|
|
565
534
|
name: "web-vue",
|
|
566
535
|
description: "Vue 3 Web frontend (Vite + Vue + TypeScript)",
|
|
567
536
|
eslint: () => `import withVue from '@vue/eslint-config-typescript'
|
|
568
|
-
import
|
|
537
|
+
import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
|
|
569
538
|
import pluginVue from 'eslint-plugin-vue'
|
|
570
539
|
|
|
571
540
|
export default [
|
|
572
541
|
...pluginVue.configs['flat/recommended'],
|
|
573
542
|
...withVue(),
|
|
574
|
-
|
|
543
|
+
prettierConfig,
|
|
575
544
|
{
|
|
576
545
|
rules: {
|
|
577
546
|
'vue/multi-word-component-names': 'off',
|
|
@@ -628,7 +597,8 @@ dist/
|
|
|
628
597
|
version: "0.2",
|
|
629
598
|
language: "en,en-US",
|
|
630
599
|
allowCompoundWords: true,
|
|
631
|
-
words: ["vite", "pinia", "vueuse", "unplugin"]
|
|
600
|
+
words: ["vite", "pinia", "vueuse", "unplugin"],
|
|
601
|
+
ignorePaths: ["**/*.svg", "**/*.png"]
|
|
632
602
|
},
|
|
633
603
|
null,
|
|
634
604
|
2
|
|
@@ -664,18 +634,9 @@ trim_trailing_whitespace = false
|
|
|
664
634
|
]
|
|
665
635
|
},
|
|
666
636
|
scripts: {
|
|
667
|
-
lint:
|
|
668
|
-
"lint:fix": 'eslint "src/**/*.{
|
|
669
|
-
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
|
|
670
|
-
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
671
|
-
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
672
|
-
"stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
|
|
673
|
-
cspell: 'cspell --gitignore "src/**/*"',
|
|
674
|
-
"type:check": "vue-tsc --noEmit",
|
|
675
|
-
"code:check": "<pm> lint && <pm> format:check",
|
|
676
|
-
"code:fix": "<pm> lint:fix && <pm> format",
|
|
677
|
-
"code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
|
|
678
|
-
"code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
|
|
637
|
+
lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && vue-tsc --noEmit && stylelint "src/**/*.{css,scss,vue}" --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
|
|
638
|
+
"lint:fix": 'eslint . --cache --cache-location node_modules/.cache/eslint --fix && stylelint "src/**/*.{css,scss,vue}" --fix --cache --cache-strategy content --cache-location node_modules/.cache/stylelint/',
|
|
639
|
+
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
|
|
679
640
|
}
|
|
680
641
|
};
|
|
681
642
|
|
|
@@ -1113,6 +1074,15 @@ function materializeVscodePreset(cwd, presetName) {
|
|
|
1113
1074
|
}
|
|
1114
1075
|
logger.log(`Local preset created at ${presetDir}`);
|
|
1115
1076
|
}
|
|
1077
|
+
function materializeVscodePresetFromBuiltin(presetName, preset) {
|
|
1078
|
+
const presetDir = getLocalPresetDir("vscode", presetName);
|
|
1079
|
+
ensureDir(presetDir);
|
|
1080
|
+
const settings = preset.settings();
|
|
1081
|
+
writeJson(path4.join(presetDir, "settings.json"), settings);
|
|
1082
|
+
const extensions = preset.extensions();
|
|
1083
|
+
writeJson(path4.join(presetDir, "extensions.json"), { recommendations: extensions });
|
|
1084
|
+
logger.log(`Local preset created at ${presetDir}`);
|
|
1085
|
+
}
|
|
1116
1086
|
var InvalidPackageJsonError = class extends Error {
|
|
1117
1087
|
constructor(filePath) {
|
|
1118
1088
|
super(`package.json exists but is not valid JSON: ${filePath}`);
|
|
@@ -1220,7 +1190,9 @@ function applyLocalVscodePreset(cwd, presetName, opts) {
|
|
|
1220
1190
|
if (extensionsData) {
|
|
1221
1191
|
let presetRecommendations = extensionsData.recommendations ?? [];
|
|
1222
1192
|
if (opts.noStylelint) {
|
|
1223
|
-
presetRecommendations = presetRecommendations.filter(
|
|
1193
|
+
presetRecommendations = presetRecommendations.filter(
|
|
1194
|
+
(ext) => ext !== STYLELINT_EXTENSION
|
|
1195
|
+
);
|
|
1224
1196
|
}
|
|
1225
1197
|
if (opts.dryRun) {
|
|
1226
1198
|
result.created.push(".vscode/extensions.json");
|
|
@@ -1273,8 +1245,10 @@ function mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result) {
|
|
|
1273
1245
|
const existingScripts = merged.scripts ?? {};
|
|
1274
1246
|
const newScripts = { ...existingScripts };
|
|
1275
1247
|
for (const [key, value] of Object.entries(templatePkg.scripts)) {
|
|
1276
|
-
|
|
1277
|
-
|
|
1248
|
+
let resolved = value.replace(/<pm>/g, prefix);
|
|
1249
|
+
if (opts.noStylelint) {
|
|
1250
|
+
resolved = resolved.replace(/\s*&&\s*stylelint\s+"[^"]*".*/g, "");
|
|
1251
|
+
}
|
|
1278
1252
|
if (existingScripts[key] !== void 0 && !opts.force) {
|
|
1279
1253
|
result.scriptsSkipped++;
|
|
1280
1254
|
if (opts.dryRun) {
|
|
@@ -1325,8 +1299,7 @@ function resolveLocalDeps(deps) {
|
|
|
1325
1299
|
function filterStylelintScripts(scripts) {
|
|
1326
1300
|
const filtered = {};
|
|
1327
1301
|
for (const [key, value] of Object.entries(scripts)) {
|
|
1328
|
-
|
|
1329
|
-
filtered[key] = value.replace(/\s*&&\s*<pm>\s+stylelint\S*/g, "");
|
|
1302
|
+
filtered[key] = value.replace(/\s*&&\s*stylelint\s+"[^"]*".*/g, "");
|
|
1330
1303
|
}
|
|
1331
1304
|
return filtered;
|
|
1332
1305
|
}
|
|
@@ -1419,18 +1392,26 @@ async function executeLocalPath(cwd, presetName, options) {
|
|
|
1419
1392
|
const existingDeps = projectPkg.devDependencies ?? {};
|
|
1420
1393
|
const missing = depsToInstall.filter((dep) => !existingDeps[dep]);
|
|
1421
1394
|
if (missing.length === 0) return;
|
|
1422
|
-
if (
|
|
1423
|
-
|
|
1424
|
-
const added = await addDepsToManifest(resolved, cwd);
|
|
1425
|
-
if (added.length > 0) {
|
|
1426
|
-
logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
|
|
1427
|
-
} else {
|
|
1428
|
-
logger.log("All dependencies already in package.json");
|
|
1429
|
-
}
|
|
1395
|
+
if (opts.dryRun) {
|
|
1396
|
+
logger.log(`[dry-run] Would add to package.json: ${missing.join(", ")}`);
|
|
1430
1397
|
return;
|
|
1431
1398
|
}
|
|
1432
|
-
if (
|
|
1433
|
-
|
|
1399
|
+
if (options.install === false) {
|
|
1400
|
+
try {
|
|
1401
|
+
const filteredTemplateDeps = Object.fromEntries(
|
|
1402
|
+
Object.entries(templatePkg.devDependencies).filter(([k]) => missing.includes(k))
|
|
1403
|
+
);
|
|
1404
|
+
const resolved = resolveLocalDeps(filteredTemplateDeps);
|
|
1405
|
+
const added = await addDepsToManifest(resolved, cwd);
|
|
1406
|
+
if (added.length > 0) {
|
|
1407
|
+
logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
|
|
1408
|
+
} else {
|
|
1409
|
+
logger.log("All dependencies already in package.json");
|
|
1410
|
+
}
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1413
|
+
logger.warn(`Failed to fetch versions: ${message}. You can add dependencies manually.`);
|
|
1414
|
+
}
|
|
1434
1415
|
return;
|
|
1435
1416
|
}
|
|
1436
1417
|
try {
|
|
@@ -1478,17 +1459,22 @@ async function executeBuiltinPath(cwd, presetName, preset, options) {
|
|
|
1478
1459
|
if (!preset.dependencies?.dev) return;
|
|
1479
1460
|
const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep) : preset.dependencies.dev;
|
|
1480
1461
|
const finalDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep) : devDeps;
|
|
1481
|
-
if (
|
|
1482
|
-
|
|
1483
|
-
if (added.length > 0) {
|
|
1484
|
-
logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
|
|
1485
|
-
} else {
|
|
1486
|
-
logger.log("All dependencies already in package.json");
|
|
1487
|
-
}
|
|
1462
|
+
if (opts.dryRun) {
|
|
1463
|
+
logger.log(`[dry-run] Would add to package.json: ${finalDeps.join(", ")}`);
|
|
1488
1464
|
return;
|
|
1489
1465
|
}
|
|
1490
|
-
if (
|
|
1491
|
-
|
|
1466
|
+
if (options.install === false) {
|
|
1467
|
+
try {
|
|
1468
|
+
const added = await addDepsToManifest(finalDeps, cwd);
|
|
1469
|
+
if (added.length > 0) {
|
|
1470
|
+
logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
|
|
1471
|
+
} else {
|
|
1472
|
+
logger.log("All dependencies already in package.json");
|
|
1473
|
+
}
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1476
|
+
logger.warn(`Failed to fetch versions: ${message}. You can add dependencies manually.`);
|
|
1477
|
+
}
|
|
1492
1478
|
return;
|
|
1493
1479
|
}
|
|
1494
1480
|
try {
|
|
@@ -1622,206 +1608,6 @@ var INIT_TOOLS = [
|
|
|
1622
1608
|
}
|
|
1623
1609
|
];
|
|
1624
1610
|
|
|
1625
|
-
// src/generators/init.ts
|
|
1626
|
-
import fs4 from "fs";
|
|
1627
|
-
import path6 from "path";
|
|
1628
|
-
function resolveSkillsDir() {
|
|
1629
|
-
const entryDir = path6.dirname(process.argv[1] ?? "");
|
|
1630
|
-
return path6.resolve(entryDir, "skills");
|
|
1631
|
-
}
|
|
1632
|
-
function listFilesRecursive(dir, base) {
|
|
1633
|
-
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
1634
|
-
const files = [];
|
|
1635
|
-
for (const entry of entries) {
|
|
1636
|
-
const childBase = `${base}/${entry.name}`;
|
|
1637
|
-
const fullPath = path6.join(dir, entry.name);
|
|
1638
|
-
if (entry.isDirectory()) {
|
|
1639
|
-
files.push(...listFilesRecursive(fullPath, childBase));
|
|
1640
|
-
} else {
|
|
1641
|
-
files.push(childBase);
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
return files;
|
|
1645
|
-
}
|
|
1646
|
-
function generateInitSkills(targetBaseDir, cwd) {
|
|
1647
|
-
const skillsDir = resolveSkillsDir();
|
|
1648
|
-
if (!fs4.existsSync(skillsDir)) {
|
|
1649
|
-
logger.error(`Bundled skills directory not found: ${skillsDir}`);
|
|
1650
|
-
logger.error('Please run "lux build" or reinstall lux.');
|
|
1651
|
-
return { copiedFiles: [], targetDir: targetBaseDir };
|
|
1652
|
-
}
|
|
1653
|
-
const targetPath = path6.resolve(cwd, targetBaseDir);
|
|
1654
|
-
try {
|
|
1655
|
-
fs4.cpSync(skillsDir, targetPath, { recursive: true, force: true });
|
|
1656
|
-
} catch (error) {
|
|
1657
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1658
|
-
logger.error(`Failed to copy skills to ${targetPath}: ${message}`);
|
|
1659
|
-
return { copiedFiles: [], targetDir: targetBaseDir };
|
|
1660
|
-
}
|
|
1661
|
-
const copiedFiles = fs4.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
|
|
1662
|
-
return { copiedFiles, targetDir: targetBaseDir };
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
// src/commands/init.ts
|
|
1666
|
-
function registerInitCommand(program2) {
|
|
1667
|
-
program2.command("init").description("Initialize AI coding tool skills in current project").action(async () => {
|
|
1668
|
-
const toolOptions = INIT_TOOLS.map((tool2) => ({
|
|
1669
|
-
value: tool2.name,
|
|
1670
|
-
label: tool2.label
|
|
1671
|
-
}));
|
|
1672
|
-
const selected = await select({
|
|
1673
|
-
message: "Which AI coding tool do you use?",
|
|
1674
|
-
options: toolOptions
|
|
1675
|
-
});
|
|
1676
|
-
if (isCancel(selected)) {
|
|
1677
|
-
cancel("Operation cancelled.");
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
const tool = INIT_TOOLS.find((t) => t.name === selected);
|
|
1681
|
-
if (!tool) {
|
|
1682
|
-
logger.error(`Unknown tool: ${String(selected)}`);
|
|
1683
|
-
return;
|
|
1684
|
-
}
|
|
1685
|
-
const cwd = process.cwd();
|
|
1686
|
-
const result = generateInitSkills(tool.targetDir, cwd);
|
|
1687
|
-
if (result.copiedFiles.length === 0) {
|
|
1688
|
-
logger.warn("No skill files were copied.");
|
|
1689
|
-
return;
|
|
1690
|
-
}
|
|
1691
|
-
for (const file of result.copiedFiles) {
|
|
1692
|
-
logger.log(` ${file}`);
|
|
1693
|
-
}
|
|
1694
|
-
outro(`Skills installed to ${tool.targetDir}/`);
|
|
1695
|
-
});
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
// src/utils/config.ts
|
|
1699
|
-
import fs5 from "fs";
|
|
1700
|
-
import os2 from "os";
|
|
1701
|
-
import path7 from "path";
|
|
1702
|
-
var CONFIG_DIR = ".lux";
|
|
1703
|
-
var ENV_FILE = "env.txt";
|
|
1704
|
-
function getEnvConfigPath() {
|
|
1705
|
-
return path7.join(os2.homedir(), CONFIG_DIR, ENV_FILE);
|
|
1706
|
-
}
|
|
1707
|
-
function getEnvConfig() {
|
|
1708
|
-
let content;
|
|
1709
|
-
try {
|
|
1710
|
-
content = fs5.readFileSync(getEnvConfigPath(), "utf-8");
|
|
1711
|
-
} catch {
|
|
1712
|
-
return {};
|
|
1713
|
-
}
|
|
1714
|
-
const result = {};
|
|
1715
|
-
for (const line of content.split("\n")) {
|
|
1716
|
-
const trimmed = line.trim();
|
|
1717
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1718
|
-
const eqIndex = trimmed.indexOf("=");
|
|
1719
|
-
if (eqIndex === -1) continue;
|
|
1720
|
-
const key = trimmed.slice(0, eqIndex).trim();
|
|
1721
|
-
const raw = trimmed.slice(eqIndex + 1).trim();
|
|
1722
|
-
const value = raw.replace(/^["']|["']$/g, "");
|
|
1723
|
-
if (key) result[key] = value;
|
|
1724
|
-
}
|
|
1725
|
-
return result;
|
|
1726
|
-
}
|
|
1727
|
-
function setEnvConfig(data) {
|
|
1728
|
-
const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
|
|
1729
|
-
writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
|
|
1730
|
-
}
|
|
1731
|
-
function clearEnvConfig() {
|
|
1732
|
-
try {
|
|
1733
|
-
fs5.unlinkSync(getEnvConfigPath());
|
|
1734
|
-
} catch {
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
// src/commands/show.ts
|
|
1739
|
-
function handleShowEnv() {
|
|
1740
|
-
const config = getEnvConfig();
|
|
1741
|
-
const entries = Object.entries(config);
|
|
1742
|
-
if (entries.length === 0) {
|
|
1743
|
-
logger.log("No env config.");
|
|
1744
|
-
return;
|
|
1745
|
-
}
|
|
1746
|
-
for (const [key, value] of entries) {
|
|
1747
|
-
logger.log(`${key}="${value}"`);
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
function registerShowCommand(program2) {
|
|
1751
|
-
const show = program2.command("show");
|
|
1752
|
-
show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
// src/utils/version.ts
|
|
1756
|
-
import { existsSync, readFileSync } from "fs";
|
|
1757
|
-
import { dirname, join } from "path";
|
|
1758
|
-
import { fileURLToPath } from "url";
|
|
1759
|
-
var PACKAGE_NAME = "@luxkit/cli";
|
|
1760
|
-
var cachedVersion;
|
|
1761
|
-
function getCurrentVersion() {
|
|
1762
|
-
if (cachedVersion) return cachedVersion;
|
|
1763
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1764
|
-
const candidates = [
|
|
1765
|
-
join(__dirname, "..", "package.json"),
|
|
1766
|
-
join(__dirname, "..", "..", "package.json")
|
|
1767
|
-
];
|
|
1768
|
-
const pkgPath = candidates.find((p) => existsSync(p));
|
|
1769
|
-
if (!pkgPath) {
|
|
1770
|
-
throw new Error("Cannot locate package.json for version reading");
|
|
1771
|
-
}
|
|
1772
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
1773
|
-
cachedVersion = pkg.version;
|
|
1774
|
-
return cachedVersion;
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
// src/commands/update.ts
|
|
1778
|
-
var GLOBAL_UPDATE_CMDS = {
|
|
1779
|
-
npm: ["npm", ["install", "-g", `${PACKAGE_NAME}@latest`]],
|
|
1780
|
-
bun: ["bun", ["install", "-g", `${PACKAGE_NAME}@latest`]]
|
|
1781
|
-
};
|
|
1782
|
-
function detectGlobalPackageManager() {
|
|
1783
|
-
return process.execPath.toLowerCase().includes("bun") ? "bun" : "npm";
|
|
1784
|
-
}
|
|
1785
|
-
async function fetchLatestVersion() {
|
|
1786
|
-
const { stdout, exitCode } = await execFileNoThrow("npm", ["view", PACKAGE_NAME, "version"]);
|
|
1787
|
-
if (exitCode !== 0 || !stdout) {
|
|
1788
|
-
throw new Error(`Failed to fetch latest version from npm registry.`);
|
|
1789
|
-
}
|
|
1790
|
-
const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
|
|
1791
|
-
return lines[lines.length - 1].trim();
|
|
1792
|
-
}
|
|
1793
|
-
async function performUpdate(pm) {
|
|
1794
|
-
const [command, args] = GLOBAL_UPDATE_CMDS[pm];
|
|
1795
|
-
const { exitCode, stderr } = await execFileNoThrow(command, args);
|
|
1796
|
-
if (exitCode !== 0) {
|
|
1797
|
-
const hint = /EACCES|permission/i.test(stderr) ? "\nTry running with elevated privileges (e.g. sudo)." : "";
|
|
1798
|
-
throw new Error(`Update failed: ${stderr}${hint}`);
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
function registerUpdateCommand(program2) {
|
|
1802
|
-
program2.command("update").description("Update @luxkit/cli to the latest version").option("--check", "Only check for updates without installing").action(async (options) => {
|
|
1803
|
-
try {
|
|
1804
|
-
const current = getCurrentVersion();
|
|
1805
|
-
const latest = await fetchLatestVersion();
|
|
1806
|
-
if (current === latest) {
|
|
1807
|
-
logger.log(`Already up to date (v${current})`);
|
|
1808
|
-
return;
|
|
1809
|
-
}
|
|
1810
|
-
if (options.check) {
|
|
1811
|
-
logger.log(`Update available: v${current} \u2192 v${latest}`);
|
|
1812
|
-
return;
|
|
1813
|
-
}
|
|
1814
|
-
const pm = detectGlobalPackageManager();
|
|
1815
|
-
await performUpdate(pm);
|
|
1816
|
-
logger.log(`Updated to v${latest}`);
|
|
1817
|
-
} catch (err) {
|
|
1818
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1819
|
-
logger.error(message);
|
|
1820
|
-
logger.warn(`You can also update manually: npm install -g ${PACKAGE_NAME}@latest`);
|
|
1821
|
-
}
|
|
1822
|
-
});
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
1611
|
// src/presets/vscode/web-vue.ts
|
|
1826
1612
|
var webVueVscode = {
|
|
1827
1613
|
name: "web-vue",
|
|
@@ -2580,6 +2366,226 @@ var VSCODE_PRESETS = [
|
|
|
2580
2366
|
goVscode
|
|
2581
2367
|
];
|
|
2582
2368
|
|
|
2369
|
+
// src/generators/init.ts
|
|
2370
|
+
import fs4 from "fs";
|
|
2371
|
+
import path6 from "path";
|
|
2372
|
+
function resolveSkillsDir() {
|
|
2373
|
+
const entryDir = path6.dirname(process.argv[1] ?? "");
|
|
2374
|
+
return path6.resolve(entryDir, "skills");
|
|
2375
|
+
}
|
|
2376
|
+
function listFilesRecursive(dir, base) {
|
|
2377
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
2378
|
+
const files = [];
|
|
2379
|
+
for (const entry of entries) {
|
|
2380
|
+
const childBase = `${base}/${entry.name}`;
|
|
2381
|
+
const fullPath = path6.join(dir, entry.name);
|
|
2382
|
+
if (entry.isDirectory()) {
|
|
2383
|
+
files.push(...listFilesRecursive(fullPath, childBase));
|
|
2384
|
+
} else {
|
|
2385
|
+
files.push(childBase);
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
return files;
|
|
2389
|
+
}
|
|
2390
|
+
function generateInitSkills(targetBaseDir, cwd) {
|
|
2391
|
+
const skillsDir = resolveSkillsDir();
|
|
2392
|
+
if (!fs4.existsSync(skillsDir)) {
|
|
2393
|
+
logger.error(`Bundled skills directory not found: ${skillsDir}`);
|
|
2394
|
+
logger.error('Please run "lux build" or reinstall lux.');
|
|
2395
|
+
return { copiedFiles: [], targetDir: targetBaseDir };
|
|
2396
|
+
}
|
|
2397
|
+
const targetPath = path6.resolve(cwd, targetBaseDir);
|
|
2398
|
+
try {
|
|
2399
|
+
fs4.cpSync(skillsDir, targetPath, { recursive: true, force: true });
|
|
2400
|
+
} catch (error) {
|
|
2401
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2402
|
+
logger.error(`Failed to copy skills to ${targetPath}: ${message}`);
|
|
2403
|
+
return { copiedFiles: [], targetDir: targetBaseDir };
|
|
2404
|
+
}
|
|
2405
|
+
const copiedFiles = fs4.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
|
|
2406
|
+
return { copiedFiles, targetDir: targetBaseDir };
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
// src/commands/init.ts
|
|
2410
|
+
function registerInitCommand(program2) {
|
|
2411
|
+
program2.command("init").description("Initialize skills or materialize presets").option("--preset", "Materialize all presets to ~/.lux/preset/ without writing to cwd").action(async (options) => {
|
|
2412
|
+
if (options.preset) {
|
|
2413
|
+
materializeAllPresets();
|
|
2414
|
+
return;
|
|
2415
|
+
}
|
|
2416
|
+
const toolOptions = INIT_TOOLS.map((tool2) => ({
|
|
2417
|
+
value: tool2.name,
|
|
2418
|
+
label: tool2.label
|
|
2419
|
+
}));
|
|
2420
|
+
const selected = await select({
|
|
2421
|
+
message: "Which AI coding tool do you use?",
|
|
2422
|
+
options: toolOptions
|
|
2423
|
+
});
|
|
2424
|
+
if (isCancel(selected)) {
|
|
2425
|
+
cancel("Operation cancelled.");
|
|
2426
|
+
return;
|
|
2427
|
+
}
|
|
2428
|
+
const tool = INIT_TOOLS.find((t) => t.name === selected);
|
|
2429
|
+
if (!tool) {
|
|
2430
|
+
logger.error(`Unknown tool: ${String(selected)}`);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
const cwd = process.cwd();
|
|
2434
|
+
const result = generateInitSkills(tool.targetDir, cwd);
|
|
2435
|
+
if (result.copiedFiles.length === 0) {
|
|
2436
|
+
logger.warn("No skill files were copied.");
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
for (const file of result.copiedFiles) {
|
|
2440
|
+
logger.log(` ${file}`);
|
|
2441
|
+
}
|
|
2442
|
+
outro(`Skills installed to ${tool.targetDir}/`);
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
function materializeAllPresets() {
|
|
2446
|
+
const opts = {
|
|
2447
|
+
cwd: process.cwd(),
|
|
2448
|
+
force: false,
|
|
2449
|
+
dryRun: false,
|
|
2450
|
+
noStylelint: false,
|
|
2451
|
+
noEditorconfig: false
|
|
2452
|
+
};
|
|
2453
|
+
for (const preset of FMT_PRESETS) {
|
|
2454
|
+
materializeFmtPreset(preset.name, preset, opts);
|
|
2455
|
+
}
|
|
2456
|
+
for (const preset of VSCODE_PRESETS) {
|
|
2457
|
+
materializeVscodePresetFromBuiltin(preset.name, preset);
|
|
2458
|
+
}
|
|
2459
|
+
logger.success("All presets materialized to ~/.lux/preset/");
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// src/utils/config.ts
|
|
2463
|
+
import fs5 from "fs";
|
|
2464
|
+
import os2 from "os";
|
|
2465
|
+
import path7 from "path";
|
|
2466
|
+
var CONFIG_DIR = ".lux";
|
|
2467
|
+
var ENV_FILE = "env.txt";
|
|
2468
|
+
function getEnvConfigPath() {
|
|
2469
|
+
return path7.join(os2.homedir(), CONFIG_DIR, ENV_FILE);
|
|
2470
|
+
}
|
|
2471
|
+
function getEnvConfig() {
|
|
2472
|
+
let content;
|
|
2473
|
+
try {
|
|
2474
|
+
content = fs5.readFileSync(getEnvConfigPath(), "utf-8");
|
|
2475
|
+
} catch {
|
|
2476
|
+
return {};
|
|
2477
|
+
}
|
|
2478
|
+
const result = {};
|
|
2479
|
+
for (const line of content.split("\n")) {
|
|
2480
|
+
const trimmed = line.trim();
|
|
2481
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2482
|
+
const eqIndex = trimmed.indexOf("=");
|
|
2483
|
+
if (eqIndex === -1) continue;
|
|
2484
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
2485
|
+
const raw = trimmed.slice(eqIndex + 1).trim();
|
|
2486
|
+
const value = raw.replace(/^["']|["']$/g, "");
|
|
2487
|
+
if (key) result[key] = value;
|
|
2488
|
+
}
|
|
2489
|
+
return result;
|
|
2490
|
+
}
|
|
2491
|
+
function setEnvConfig(data) {
|
|
2492
|
+
const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
|
|
2493
|
+
writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
|
|
2494
|
+
}
|
|
2495
|
+
function clearEnvConfig() {
|
|
2496
|
+
try {
|
|
2497
|
+
fs5.unlinkSync(getEnvConfigPath());
|
|
2498
|
+
} catch {
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
// src/commands/show.ts
|
|
2503
|
+
function handleShowEnv() {
|
|
2504
|
+
const config = getEnvConfig();
|
|
2505
|
+
const entries = Object.entries(config);
|
|
2506
|
+
if (entries.length === 0) {
|
|
2507
|
+
logger.log("No env config.");
|
|
2508
|
+
return;
|
|
2509
|
+
}
|
|
2510
|
+
for (const [key, value] of entries) {
|
|
2511
|
+
logger.log(`${key}="${value}"`);
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
function registerShowCommand(program2) {
|
|
2515
|
+
const show = program2.command("show");
|
|
2516
|
+
show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// src/utils/version.ts
|
|
2520
|
+
import { existsSync, readFileSync } from "fs";
|
|
2521
|
+
import { dirname, join } from "path";
|
|
2522
|
+
import { fileURLToPath } from "url";
|
|
2523
|
+
var PACKAGE_NAME = "@luxkit/cli";
|
|
2524
|
+
var cachedVersion;
|
|
2525
|
+
function getCurrentVersion() {
|
|
2526
|
+
if (cachedVersion) return cachedVersion;
|
|
2527
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
2528
|
+
const candidates = [
|
|
2529
|
+
join(__dirname, "..", "package.json"),
|
|
2530
|
+
join(__dirname, "..", "..", "package.json")
|
|
2531
|
+
];
|
|
2532
|
+
const pkgPath = candidates.find((p) => existsSync(p));
|
|
2533
|
+
if (!pkgPath) {
|
|
2534
|
+
throw new Error("Cannot locate package.json for version reading");
|
|
2535
|
+
}
|
|
2536
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
2537
|
+
cachedVersion = pkg.version;
|
|
2538
|
+
return cachedVersion;
|
|
2539
|
+
}
|
|
2540
|
+
|
|
2541
|
+
// src/commands/update.ts
|
|
2542
|
+
var GLOBAL_UPDATE_CMDS = {
|
|
2543
|
+
npm: ["npm", ["install", "-g", `${PACKAGE_NAME}@latest`]],
|
|
2544
|
+
bun: ["bun", ["install", "-g", `${PACKAGE_NAME}@latest`]]
|
|
2545
|
+
};
|
|
2546
|
+
function detectGlobalPackageManager() {
|
|
2547
|
+
return process.execPath.toLowerCase().includes("bun") ? "bun" : "npm";
|
|
2548
|
+
}
|
|
2549
|
+
async function fetchLatestVersion() {
|
|
2550
|
+
const { stdout, exitCode } = await execFileNoThrow("npm", ["view", PACKAGE_NAME, "version"]);
|
|
2551
|
+
if (exitCode !== 0 || !stdout) {
|
|
2552
|
+
throw new Error(`Failed to fetch latest version from npm registry.`);
|
|
2553
|
+
}
|
|
2554
|
+
const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
|
|
2555
|
+
return lines[lines.length - 1].trim();
|
|
2556
|
+
}
|
|
2557
|
+
async function performUpdate(pm) {
|
|
2558
|
+
const [command, args] = GLOBAL_UPDATE_CMDS[pm];
|
|
2559
|
+
const { exitCode, stderr } = await execFileNoThrow(command, args);
|
|
2560
|
+
if (exitCode !== 0) {
|
|
2561
|
+
const hint = /EACCES|permission/i.test(stderr) ? "\nTry running with elevated privileges (e.g. sudo)." : "";
|
|
2562
|
+
throw new Error(`Update failed: ${stderr}${hint}`);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
function registerUpdateCommand(program2) {
|
|
2566
|
+
program2.command("update").description("Update @luxkit/cli to the latest version").option("--check", "Only check for updates without installing").action(async (options) => {
|
|
2567
|
+
try {
|
|
2568
|
+
const current = getCurrentVersion();
|
|
2569
|
+
const latest = await fetchLatestVersion();
|
|
2570
|
+
if (current === latest) {
|
|
2571
|
+
logger.log(`Already up to date (v${current})`);
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
if (options.check) {
|
|
2575
|
+
logger.log(`Update available: v${current} \u2192 v${latest}`);
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
const pm = detectGlobalPackageManager();
|
|
2579
|
+
await performUpdate(pm);
|
|
2580
|
+
logger.log(`Updated to v${latest}`);
|
|
2581
|
+
} catch (err) {
|
|
2582
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2583
|
+
logger.error(message);
|
|
2584
|
+
logger.warn(`You can also update manually: npm install -g ${PACKAGE_NAME}@latest`);
|
|
2585
|
+
}
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2583
2589
|
// src/generators/vscode.ts
|
|
2584
2590
|
var STYLELINT_SETTINGS_PREFIXES2 = [
|
|
2585
2591
|
"stylelint.",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Custom Preset Configuration
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Always use `lux init --preset` to initialize presets.** Running `lux fmt` / `lux vscode` directly generates config files in cwd and requires `package.json` — only use in real project directories.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
lux init --preset # materialize all built-in presets to ~/.lux/preset/
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
After materialization, edit files under `~/.lux/preset/<type>/<preset-name>/` to customize. Changes take effect on the next `lux fmt` / `lux vscode` run.
|
|
12
|
+
|
|
13
|
+
`lux init --preset` can be run repeatedly — each run overwrites with built-in defaults. To reset a single preset, use `lux fmt <name> --reset` or `lux vscode <name> --reset` (deletes local preset dir only; re-generated from built-in on next run).
|
|
14
|
+
|
|
15
|
+
## Storage Location
|
|
16
|
+
|
|
17
|
+
`~/.lux/preset/` (i.e. `os.homedir()/.lux/preset/`). Override with `LUX_HOME` env var.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
~/.lux/preset/
|
|
21
|
+
├── fmt/ # fmt presets (web-vue, web-react, electron-vue, uniapp, node, nest)
|
|
22
|
+
└── vscode/ # vscode presets (same + go)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## File Reference
|
|
26
|
+
|
|
27
|
+
### fmt preset
|
|
28
|
+
|
|
29
|
+
| File | Description |
|
|
30
|
+
| :--------------------- | :--------------------------------------------- |
|
|
31
|
+
| `eslint.config.mjs` | ESLint flat config |
|
|
32
|
+
| `.prettierrc` | Prettier JSON config |
|
|
33
|
+
| `.prettierignore` | Prettier ignore rules |
|
|
34
|
+
| `stylelint.config.mjs` | Stylelint config |
|
|
35
|
+
| `.stylelintignore` | Stylelint ignore rules |
|
|
36
|
+
| `cspell.json` | CSpell dictionary config |
|
|
37
|
+
| `.editorconfig` | EditorConfig config |
|
|
38
|
+
| `package.json` | Template with `devDependencies` and `scripts` |
|
|
39
|
+
|
|
40
|
+
### vscode preset
|
|
41
|
+
|
|
42
|
+
| File | Description |
|
|
43
|
+
| :---------------- | :-------------------------- |
|
|
44
|
+
| `settings.json` | VSCode workspace settings |
|
|
45
|
+
| `extensions.json` | VSCode extension recommendations |
|
|
46
|
+
|
|
47
|
+
## Template Placeholders (fmt preset)
|
|
48
|
+
|
|
49
|
+
| Placeholder | Replaced with |
|
|
50
|
+
| :---------- | :------------ |
|
|
51
|
+
| `<pm>` | Package manager prefix (`bun run` / `pnpm run` / `npm run`) |
|
|
52
|
+
| `<lockfile>` | Project lockfile name (`bun.lock` / `pnpm-lock.yaml` etc.) |
|
|
53
|
+
| `"<latest>"` | Resolved to latest version at install time, or pin like `"3.3.0"` |
|
|
54
|
+
|
|
55
|
+
## Workflow
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
lux init --preset
|
|
59
|
+
→ materialize all built-in presets to ~/.lux/preset/ (no cwd writes)
|
|
60
|
+
|
|
61
|
+
lux fmt web-vue (in target project)
|
|
62
|
+
→ local preset exists → use local version (with custom edits)
|
|
63
|
+
→ local preset missing → generate from built-in + materialize (also writes to cwd)
|
|
64
|
+
|
|
65
|
+
lux vscode web-vue (in target project)
|
|
66
|
+
→ same as above
|
|
67
|
+
```
|
package/dist/skills/lux/skill.md
CHANGED
|
@@ -13,6 +13,7 @@ lux fmt list
|
|
|
13
13
|
- `--force` — overwrite existing config files (default: skip)
|
|
14
14
|
- `--dry-run` — preview what would be generated, write nothing
|
|
15
15
|
- `--no-install` — write deps to package.json but skip install
|
|
16
|
+
- `--reset` — reset local preset, re-materialize from built-in defaults
|
|
16
17
|
|
|
17
18
|
Presets: `web-vue` `web-react` `electron-vue` `uniapp` `node` `nest`
|
|
18
19
|
|
|
@@ -23,12 +24,17 @@ lux vscode <preset> [--dry-run]
|
|
|
23
24
|
lux vscode list
|
|
24
25
|
```
|
|
25
26
|
|
|
27
|
+
- `--force` — overwrite existing settings (default: skip)
|
|
28
|
+
- `--dry-run` — preview what would be generated, write nothing
|
|
29
|
+
- `--reset` — reset local preset, re-materialize from built-in defaults
|
|
30
|
+
|
|
26
31
|
Presets: `web-vue` `web-react` `electron-vue` `uniapp` `node` `nest` `go`
|
|
27
32
|
|
|
28
|
-
## init —
|
|
33
|
+
## init — initialize skills or presets
|
|
29
34
|
|
|
30
35
|
```bash
|
|
31
|
-
lux init
|
|
36
|
+
lux init # interactively select AI tool, copy skill files to project
|
|
37
|
+
lux init --preset # materialize all built-in presets to ~/.lux/preset/ (no cwd writes, no package.json required)
|
|
32
38
|
```
|
|
33
39
|
|
|
34
40
|
## vpn — proxy clipboard helper
|
|
@@ -53,3 +59,7 @@ lux show env # show stored proxy env
|
|
|
53
59
|
lux update # update to latest
|
|
54
60
|
lux update --check
|
|
55
61
|
```
|
|
62
|
+
|
|
63
|
+
## Custom presets
|
|
64
|
+
|
|
65
|
+
To customize preset rules, see `references/custom-preset-setting.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luxkit/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "One-click project formatting & VSCode config CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,16 +15,9 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsup && node scripts/copy-assets.mjs",
|
|
17
17
|
"dev": "tsup --watch",
|
|
18
|
-
"lint": "eslint .",
|
|
18
|
+
"lint": "eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore \"src/**/*\" && tsc --noEmit",
|
|
19
|
+
"lint:fix": "eslint . --cache --cache-location node_modules/.cache/eslint --fix",
|
|
19
20
|
"format": "prettier --write \"src/**/*.{ts,js,json}\"",
|
|
20
|
-
"cspell": "cspell --gitignore \"src/**/*\"",
|
|
21
|
-
"format:check": "prettier --check \"src/**/*.{ts,js,json}\"",
|
|
22
|
-
"type:check": "tsc --noEmit",
|
|
23
|
-
"code:check": "bun run lint && bun run format:check",
|
|
24
|
-
"code:check:all": "bun run lint && bun run format:check && bun run cspell",
|
|
25
|
-
"lint:fix": "eslint \"src/**/*.{js,ts}\" --fix",
|
|
26
|
-
"code:fix": "bun run lint:fix && bun run format",
|
|
27
|
-
"code:fix:all": "bun run lint:fix && bun run format",
|
|
28
21
|
"prepublishOnly": "cross-env NODE_ENV=production bun run build",
|
|
29
22
|
"test": "vitest run",
|
|
30
23
|
"test:watch": "vitest",
|