@luxkit/cli 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -40
- package/dist/index.js +491 -52
- package/dist/skills/lux/skill.md +55 -0
- package/package.json +19 -7
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
### What is lux?
|
|
20
20
|
|
|
21
|
-
`lux` is a CLI tool that initializes project formatting configs and VSCode workspace settings with a single command. It generates ESLint, Prettier,
|
|
21
|
+
`lux` is a CLI tool that initializes project formatting configs and VSCode workspace settings with a single command. It generates ESLint, Prettier, CSpell, EditorConfig files and VSCode settings from battle-tested presets — with smart merge and conflict resolution.
|
|
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" />
|
|
@@ -26,19 +26,19 @@
|
|
|
26
26
|
|
|
27
27
|
### ✨ Key Highlights
|
|
28
28
|
|
|
29
|
-
| Feature | Description
|
|
30
|
-
| :------------------------- |
|
|
31
|
-
| 🎯 **One Command Setup** | `lux fmt web-vue` generates all linting & formatting configs instantly
|
|
32
|
-
| 🔧 **
|
|
33
|
-
| 🖥️ **
|
|
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
|
|
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
42
|
|
|
43
43
|
<br />
|
|
44
44
|
|
|
@@ -51,11 +51,16 @@ npm install -g @luxkit/cli
|
|
|
51
51
|
bun add -g @luxkit/cli
|
|
52
52
|
|
|
53
53
|
# Initialize formatting configs
|
|
54
|
-
lux fmt web-vue # Generate ESLint, Prettier,
|
|
54
|
+
lux fmt web-vue # Generate ESLint, Prettier, CSpell
|
|
55
|
+
lux fmt web-vue --stylelint # Also include Stylelint
|
|
56
|
+
lux fmt web-vue --editorconfig # Also include EditorConfig
|
|
55
57
|
|
|
56
58
|
# Initialize VSCode settings
|
|
57
59
|
lux vscode web-vue # Generate .vscode/settings.json + extensions.json
|
|
58
60
|
|
|
61
|
+
# Initialize AI coding tool skills
|
|
62
|
+
lux init # Select tool interactively, copy skills to project
|
|
63
|
+
|
|
59
64
|
# List available presets
|
|
60
65
|
lux fmt list
|
|
61
66
|
lux vscode list
|
|
@@ -65,33 +70,34 @@ lux vscode list
|
|
|
65
70
|
|
|
66
71
|
### CLI Commands
|
|
67
72
|
|
|
68
|
-
| Command | Description
|
|
69
|
-
| :-------------------------- |
|
|
70
|
-
| `lux fmt <preset>` | Initialize formatting config files
|
|
71
|
-
| `lux fmt list` | List available fmt presets
|
|
72
|
-
| `lux vscode <preset>` | Initialize VSCode workspace settings
|
|
73
|
-
| `lux vscode list` | List available VSCode presets
|
|
73
|
+
| Command | Description |
|
|
74
|
+
| :-------------------------- | :---------------------------------------------------------------- |
|
|
75
|
+
| `lux fmt <preset>` | Initialize formatting config files |
|
|
76
|
+
| `lux fmt list` | List available fmt presets |
|
|
77
|
+
| `lux vscode <preset>` | Initialize VSCode workspace settings |
|
|
78
|
+
| `lux vscode list` | List available VSCode presets |
|
|
79
|
+
| `lux init` | Initialize AI coding tool skills in current project |
|
|
74
80
|
| `lux set <key=value> [...]` | Persist proxy env vars (e.g. `https_proxy=http://127.0.0.1:7890`) |
|
|
75
|
-
| `lux unset` | Clear all stored proxy configuration
|
|
76
|
-
| `lux show env` | Display stored proxy environment variables
|
|
77
|
-
| `lux vpn cmd` | Copy CMD proxy commands to clipboard
|
|
78
|
-
| `lux vpn pw` | Copy PowerShell proxy commands to clipboard
|
|
79
|
-
| `lux vpn bash` | Copy Bash proxy commands to clipboard
|
|
80
|
-
| `lux update` | Update `@luxkit/cli` to the latest version
|
|
81
|
-
| `lux update --check` | Check for available updates without installing
|
|
81
|
+
| `lux unset` | Clear all stored proxy configuration |
|
|
82
|
+
| `lux show env` | Display stored proxy environment variables |
|
|
83
|
+
| `lux vpn cmd` | Copy CMD proxy commands to clipboard |
|
|
84
|
+
| `lux vpn pw` | Copy PowerShell proxy commands to clipboard |
|
|
85
|
+
| `lux vpn bash` | Copy Bash proxy commands to clipboard |
|
|
86
|
+
| `lux update` | Update `@luxkit/cli` to the latest version |
|
|
87
|
+
| `lux update --check` | Check for available updates without installing |
|
|
82
88
|
|
|
83
89
|
<br />
|
|
84
90
|
|
|
85
91
|
### Available Presets
|
|
86
92
|
|
|
87
|
-
| Preset
|
|
88
|
-
|
|
|
89
|
-
| `web-vue`
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `node`
|
|
93
|
-
| `nest`
|
|
94
|
-
| `go`
|
|
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 |
|
|
95
101
|
|
|
96
102
|
<br />
|
|
97
103
|
|
|
@@ -100,9 +106,11 @@ lux vscode list
|
|
|
100
106
|
```bash
|
|
101
107
|
lux fmt <preset> [options]
|
|
102
108
|
|
|
103
|
-
--force
|
|
104
|
-
--no-install
|
|
105
|
-
--dry-run
|
|
109
|
+
--force Force overwrite existing files
|
|
110
|
+
--no-install Skip dependency installation
|
|
111
|
+
--dry-run Preview without writing files
|
|
112
|
+
--stylelint Include Stylelint config generation (opt-in)
|
|
113
|
+
--editorconfig Include EditorConfig config generation (opt-in)
|
|
106
114
|
```
|
|
107
115
|
|
|
108
116
|
<br />
|
|
@@ -142,7 +150,7 @@ lux fmt web-vue
|
|
|
142
150
|
| Test | Vitest (unit + acceptance) |
|
|
143
151
|
| CLI | Commander.js |
|
|
144
152
|
| Output | Chalk |
|
|
145
|
-
| Bundle |
|
|
153
|
+
| Bundle | Minimal runtime deps (chalk + commander + @clack/prompts) |
|
|
146
154
|
|
|
147
155
|
<br />
|
|
148
156
|
|
package/dist/index.js
CHANGED
|
@@ -114,8 +114,8 @@ trim_trailing_whitespace = false
|
|
|
114
114
|
]
|
|
115
115
|
},
|
|
116
116
|
scripts: {
|
|
117
|
-
lint: "eslint .",
|
|
118
|
-
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix',
|
|
117
|
+
lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
|
|
118
|
+
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
|
|
119
119
|
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
120
120
|
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
121
121
|
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
@@ -297,8 +297,8 @@ trim_trailing_whitespace = false
|
|
|
297
297
|
]
|
|
298
298
|
},
|
|
299
299
|
scripts: {
|
|
300
|
-
lint: "eslint .",
|
|
301
|
-
"lint:fix": 'eslint "src/**/*.{js,ts}" --fix',
|
|
300
|
+
lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
|
|
301
|
+
"lint:fix": 'eslint "src/**/*.{js,ts}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
|
|
302
302
|
format: 'prettier --write "src/**/*.{ts,js,json}"',
|
|
303
303
|
"format:check": 'prettier --check "src/**/*.{ts,js,json}"',
|
|
304
304
|
cspell: 'cspell --gitignore "src/**/*"',
|
|
@@ -416,8 +416,8 @@ trim_trailing_whitespace = false
|
|
|
416
416
|
]
|
|
417
417
|
},
|
|
418
418
|
scripts: {
|
|
419
|
-
lint: "eslint .",
|
|
420
|
-
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix',
|
|
419
|
+
lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
|
|
420
|
+
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
|
|
421
421
|
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
422
422
|
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
423
423
|
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
@@ -431,6 +431,134 @@ trim_trailing_whitespace = false
|
|
|
431
431
|
}
|
|
432
432
|
};
|
|
433
433
|
|
|
434
|
+
// src/presets/fmt/web-react.ts
|
|
435
|
+
var webReactFmt = {
|
|
436
|
+
name: "web-react",
|
|
437
|
+
description: "React Web frontend (Vite + React + TypeScript)",
|
|
438
|
+
eslint: () => `import js from '@eslint/js'
|
|
439
|
+
import tseslint from 'typescript-eslint'
|
|
440
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
441
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
442
|
+
import globals from 'globals'
|
|
443
|
+
import prettierConfig from 'eslint-config-prettier'
|
|
444
|
+
|
|
445
|
+
export default tseslint.config(
|
|
446
|
+
{ ignores: ['dist'] },
|
|
447
|
+
{
|
|
448
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
449
|
+
files: ['**/*.{ts,tsx}'],
|
|
450
|
+
languageOptions: {
|
|
451
|
+
ecmaVersion: 2020,
|
|
452
|
+
globals: globals.browser,
|
|
453
|
+
},
|
|
454
|
+
plugins: {
|
|
455
|
+
'react-hooks': reactHooks,
|
|
456
|
+
'react-refresh': reactRefresh,
|
|
457
|
+
},
|
|
458
|
+
rules: {
|
|
459
|
+
...reactHooks.configs.recommended.rules,
|
|
460
|
+
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
|
461
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
462
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
prettierConfig,
|
|
466
|
+
)
|
|
467
|
+
`,
|
|
468
|
+
prettier: () => JSON.stringify(
|
|
469
|
+
{
|
|
470
|
+
semi: false,
|
|
471
|
+
singleQuote: true,
|
|
472
|
+
tabWidth: 2,
|
|
473
|
+
trailingComma: "all",
|
|
474
|
+
printWidth: 100,
|
|
475
|
+
endOfLine: "lf"
|
|
476
|
+
},
|
|
477
|
+
null,
|
|
478
|
+
2
|
|
479
|
+
) + "\n",
|
|
480
|
+
prettierIgnore: () => `node_modules/
|
|
481
|
+
<lockfile>
|
|
482
|
+
dist/
|
|
483
|
+
coverage/
|
|
484
|
+
`,
|
|
485
|
+
stylelint: () => `export default {
|
|
486
|
+
plugins: ['stylelint-order', '@stylistic/stylelint-plugin'],
|
|
487
|
+
extends: [
|
|
488
|
+
'stylelint-config-standard-scss',
|
|
489
|
+
'stylelint-config-recess-order',
|
|
490
|
+
],
|
|
491
|
+
rules: {
|
|
492
|
+
'selector-class-pattern': null,
|
|
493
|
+
'scss/dollar-variable-pattern': null,
|
|
494
|
+
'scss/percent-placeholder-pattern': null,
|
|
495
|
+
'scss/at-mixin-pattern': null,
|
|
496
|
+
'order/properties-order': null,
|
|
497
|
+
},
|
|
498
|
+
}
|
|
499
|
+
`,
|
|
500
|
+
stylelintIgnore: () => `node_modules/
|
|
501
|
+
dist/
|
|
502
|
+
`,
|
|
503
|
+
cspell: () => JSON.stringify(
|
|
504
|
+
{
|
|
505
|
+
$schema: "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
|
|
506
|
+
version: "0.2",
|
|
507
|
+
language: "en,en-US",
|
|
508
|
+
allowCompoundWords: true,
|
|
509
|
+
words: ["vite", "react", "zustand", "tanstack"]
|
|
510
|
+
},
|
|
511
|
+
null,
|
|
512
|
+
2
|
|
513
|
+
) + "\n",
|
|
514
|
+
editorconfig: () => `root = true
|
|
515
|
+
|
|
516
|
+
[*]
|
|
517
|
+
charset = utf-8
|
|
518
|
+
indent_style = space
|
|
519
|
+
indent_size = 2
|
|
520
|
+
end_of_line = lf
|
|
521
|
+
insert_final_newline = true
|
|
522
|
+
trim_trailing_whitespace = true
|
|
523
|
+
|
|
524
|
+
[*.md]
|
|
525
|
+
trim_trailing_whitespace = false
|
|
526
|
+
`,
|
|
527
|
+
dependencies: {
|
|
528
|
+
dev: [
|
|
529
|
+
"eslint",
|
|
530
|
+
"@eslint/js",
|
|
531
|
+
"typescript-eslint",
|
|
532
|
+
"eslint-plugin-react-hooks",
|
|
533
|
+
"eslint-plugin-react-refresh",
|
|
534
|
+
"eslint-config-prettier",
|
|
535
|
+
"globals",
|
|
536
|
+
"prettier",
|
|
537
|
+
"stylelint",
|
|
538
|
+
"stylelint-config-standard-scss",
|
|
539
|
+
"stylelint-order",
|
|
540
|
+
"stylelint-scss",
|
|
541
|
+
"@stylistic/stylelint-plugin",
|
|
542
|
+
"postcss-scss",
|
|
543
|
+
"cspell"
|
|
544
|
+
]
|
|
545
|
+
},
|
|
546
|
+
scripts: {
|
|
547
|
+
lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
|
|
548
|
+
"lint:fix": 'eslint "src/**/*.{js,ts,jsx,tsx}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
|
|
549
|
+
format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"',
|
|
550
|
+
"format:check": 'prettier --check "src/**/*.{ts,js,json,jsx,tsx,css,scss}"',
|
|
551
|
+
stylelint: 'stylelint "src/**/*.{css,scss}"',
|
|
552
|
+
"stylelint:fix": 'stylelint "src/**/*.{css,scss}" --fix',
|
|
553
|
+
cspell: 'cspell --gitignore "src/**/*"',
|
|
554
|
+
"type:check": "tsc --noEmit",
|
|
555
|
+
"code:check": "<pm> lint && <pm> format:check",
|
|
556
|
+
"code:fix": "<pm> lint:fix && <pm> format",
|
|
557
|
+
"code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
|
|
558
|
+
"code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
434
562
|
// src/presets/fmt/web-vue.ts
|
|
435
563
|
var webVueFmt = {
|
|
436
564
|
name: "web-vue",
|
|
@@ -535,8 +663,8 @@ trim_trailing_whitespace = false
|
|
|
535
663
|
]
|
|
536
664
|
},
|
|
537
665
|
scripts: {
|
|
538
|
-
lint: "eslint .",
|
|
539
|
-
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix',
|
|
666
|
+
lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
|
|
667
|
+
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
|
|
540
668
|
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
541
669
|
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
542
670
|
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
@@ -551,7 +679,14 @@ trim_trailing_whitespace = false
|
|
|
551
679
|
};
|
|
552
680
|
|
|
553
681
|
// src/presets/fmt/index.ts
|
|
554
|
-
var FMT_PRESETS = [
|
|
682
|
+
var FMT_PRESETS = [
|
|
683
|
+
webVueFmt,
|
|
684
|
+
webReactFmt,
|
|
685
|
+
electronVueFmt,
|
|
686
|
+
uniappFmt,
|
|
687
|
+
nodeFmt,
|
|
688
|
+
nestFmt
|
|
689
|
+
];
|
|
555
690
|
|
|
556
691
|
// src/utils/logger.ts
|
|
557
692
|
import chalk from "chalk";
|
|
@@ -667,7 +802,10 @@ function readJson(filePath) {
|
|
|
667
802
|
try {
|
|
668
803
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
669
804
|
return JSON.parse(raw);
|
|
670
|
-
} catch {
|
|
805
|
+
} catch (error) {
|
|
806
|
+
if (error.code === "ENOENT") return null;
|
|
807
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
808
|
+
logger.error(`Failed to read or parse ${path.basename(filePath)}: ${message}`);
|
|
671
809
|
return null;
|
|
672
810
|
}
|
|
673
811
|
}
|
|
@@ -693,13 +831,20 @@ function generateConfigFile(preset, filename, content, opts) {
|
|
|
693
831
|
if (action === "skip") return "skipped";
|
|
694
832
|
if (opts.dryRun) return exists ? "overwritten" : "created";
|
|
695
833
|
const resolved = opts.lockfile ? content.replace(/<lockfile>/g, opts.lockfile) : content.replace(/<lockfile>\n?/g, "");
|
|
696
|
-
|
|
834
|
+
try {
|
|
835
|
+
writeFile(filepath, resolved);
|
|
836
|
+
} catch (error) {
|
|
837
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
838
|
+
logger.error(`Failed to write ${filename}: ${message}`);
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
697
841
|
return exists ? "overwritten" : "created";
|
|
698
842
|
}
|
|
699
843
|
function generateAllFmt(preset, opts) {
|
|
700
844
|
const result = { created: [], overwritten: [], skipped: [] };
|
|
701
845
|
for (const { filename, getContent } of CONFIG_FILES) {
|
|
702
846
|
if (opts.noStylelint && filename.includes("stylelint")) continue;
|
|
847
|
+
if (opts.noEditorconfig && filename === ".editorconfig") continue;
|
|
703
848
|
const content = getContent(preset);
|
|
704
849
|
if (content === void 0) continue;
|
|
705
850
|
const action = generateConfigFile(preset, filename, content, opts);
|
|
@@ -713,6 +858,29 @@ function generateAllFmt(preset, opts) {
|
|
|
713
858
|
// src/utils/deps.ts
|
|
714
859
|
import path3 from "path";
|
|
715
860
|
import { spawn } from "child_process";
|
|
861
|
+
|
|
862
|
+
// src/utils/execFileNoThrow.ts
|
|
863
|
+
import { exec } from "child_process";
|
|
864
|
+
import { promisify } from "util";
|
|
865
|
+
var execAsync = promisify(exec);
|
|
866
|
+
async function execFileNoThrow(command, args, options) {
|
|
867
|
+
const cmdStr = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
868
|
+
try {
|
|
869
|
+
const { stdout, stderr } = await execAsync(cmdStr, {
|
|
870
|
+
cwd: options?.cwd
|
|
871
|
+
});
|
|
872
|
+
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 };
|
|
873
|
+
} catch (err) {
|
|
874
|
+
const error = err;
|
|
875
|
+
return {
|
|
876
|
+
stdout: (error.stdout ?? "").trim(),
|
|
877
|
+
stderr: (error.stderr ?? "").trim(),
|
|
878
|
+
exitCode: error.code === "ENOENT" ? null : 1
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// src/utils/deps.ts
|
|
716
884
|
function detectPackageManager(cwd) {
|
|
717
885
|
if (fileExists(`${cwd}/bun.lockb`) || fileExists(`${cwd}/bun.lock`)) return "bun";
|
|
718
886
|
if (fileExists(`${cwd}/pnpm-lock.yaml`)) return "pnpm";
|
|
@@ -743,6 +911,37 @@ function getRunPrefix(pm) {
|
|
|
743
911
|
return "npm run";
|
|
744
912
|
}
|
|
745
913
|
}
|
|
914
|
+
async function fetchPackageVersion(pkg) {
|
|
915
|
+
const { stdout, exitCode } = await execFileNoThrow("npm", ["view", pkg, "version"]);
|
|
916
|
+
if (exitCode !== 0 || !stdout) {
|
|
917
|
+
throw new Error(`Failed to fetch version for "${pkg}" from npm registry.`);
|
|
918
|
+
}
|
|
919
|
+
const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
|
|
920
|
+
return lines[lines.length - 1].trim();
|
|
921
|
+
}
|
|
922
|
+
async function addDepsToManifest(packages, cwd) {
|
|
923
|
+
const pkgPath = path3.join(cwd, "package.json");
|
|
924
|
+
const pkg = readJson(pkgPath);
|
|
925
|
+
if (!pkg) {
|
|
926
|
+
throw new Error("package.json not found");
|
|
927
|
+
}
|
|
928
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
929
|
+
const missing = packages.filter((p) => !devDeps[p]);
|
|
930
|
+
if (missing.length === 0) return [];
|
|
931
|
+
const results = await Promise.all(
|
|
932
|
+
missing.map(async (pkgName) => {
|
|
933
|
+
const version = await fetchPackageVersion(pkgName);
|
|
934
|
+
return { pkgName, version };
|
|
935
|
+
})
|
|
936
|
+
);
|
|
937
|
+
const updatedDevDeps = { ...devDeps };
|
|
938
|
+
for (const { pkgName, version } of results) {
|
|
939
|
+
updatedDevDeps[pkgName] = `^${version}`;
|
|
940
|
+
}
|
|
941
|
+
pkg.devDependencies = updatedDevDeps;
|
|
942
|
+
writeJson(pkgPath, pkg);
|
|
943
|
+
return results.map((r) => r.pkgName);
|
|
944
|
+
}
|
|
746
945
|
async function installDevDeps(packages, cwd, pm) {
|
|
747
946
|
const manager = pm ?? detectPackageManager(cwd);
|
|
748
947
|
const pkg = readJson(path3.join(cwd, "package.json"));
|
|
@@ -794,9 +993,12 @@ function isNotStylelintDep(dep) {
|
|
|
794
993
|
if (dep === "postcss-html" || dep === "postcss-scss") return false;
|
|
795
994
|
return true;
|
|
796
995
|
}
|
|
996
|
+
function isNotEditorconfigDep(dep) {
|
|
997
|
+
return !dep.includes("editorconfig");
|
|
998
|
+
}
|
|
797
999
|
function registerFmtCommand(program2) {
|
|
798
1000
|
const fmt = program2.command("fmt").description("Initialize formatting config with preset");
|
|
799
|
-
fmt.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--no-install", "Skip dependency installation").option("--dry-run", "Preview without writing files").option("--
|
|
1001
|
+
fmt.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--no-install", "Skip dependency installation").option("--dry-run", "Preview without writing files").option("--stylelint", "Include Stylelint config generation").option("--editorconfig", "Include EditorConfig config generation").action(
|
|
800
1002
|
async (presetName, options) => {
|
|
801
1003
|
const preset = resolvePreset(FMT_PRESETS, presetName);
|
|
802
1004
|
if (!preset) return;
|
|
@@ -806,7 +1008,8 @@ function registerFmtCommand(program2) {
|
|
|
806
1008
|
cwd,
|
|
807
1009
|
force: options.force ?? false,
|
|
808
1010
|
dryRun: options.dryRun ?? false,
|
|
809
|
-
noStylelint: options.stylelint
|
|
1011
|
+
noStylelint: options.stylelint !== true,
|
|
1012
|
+
noEditorconfig: options.editorconfig !== true,
|
|
810
1013
|
lockfile: pm ? getLockfileName(pm) : void 0
|
|
811
1014
|
};
|
|
812
1015
|
const result = generateAllFmt(preset, opts);
|
|
@@ -826,20 +1029,27 @@ function registerFmtCommand(program2) {
|
|
|
826
1029
|
}
|
|
827
1030
|
if (!preset.dependencies?.dev) return;
|
|
828
1031
|
const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep) : preset.dependencies.dev;
|
|
1032
|
+
const finalDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep) : devDeps;
|
|
829
1033
|
if (options.install === false) {
|
|
830
|
-
|
|
1034
|
+
const added = await addDepsToManifest(finalDeps, cwd);
|
|
1035
|
+
if (added.length > 0) {
|
|
1036
|
+
logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
|
|
1037
|
+
} else {
|
|
1038
|
+
logger.log("All dependencies already in package.json");
|
|
1039
|
+
}
|
|
831
1040
|
return;
|
|
832
1041
|
}
|
|
833
1042
|
if (opts.dryRun) {
|
|
834
|
-
logger.log(`[dry-run] Would install: ${
|
|
1043
|
+
logger.log(`[dry-run] Would install: ${finalDeps.join(", ")}`);
|
|
835
1044
|
return;
|
|
836
1045
|
}
|
|
837
1046
|
try {
|
|
838
1047
|
logger.log(`Installing dependencies with ${pm}...`);
|
|
839
|
-
await installDevDeps(
|
|
1048
|
+
await installDevDeps(finalDeps, cwd, pm);
|
|
840
1049
|
logger.success("Dependencies installed successfully");
|
|
841
|
-
} catch {
|
|
842
|
-
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1052
|
+
logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
|
|
843
1053
|
}
|
|
844
1054
|
}
|
|
845
1055
|
);
|
|
@@ -931,19 +1141,109 @@ async function injectScripts(scripts, opts, pm) {
|
|
|
931
1141
|
}
|
|
932
1142
|
}
|
|
933
1143
|
|
|
934
|
-
// src/
|
|
1144
|
+
// src/commands/init.ts
|
|
1145
|
+
import { select, isCancel, cancel, outro } from "@clack/prompts";
|
|
1146
|
+
|
|
1147
|
+
// src/presets/init.ts
|
|
1148
|
+
var INIT_TOOLS = [
|
|
1149
|
+
{
|
|
1150
|
+
name: "claude",
|
|
1151
|
+
label: "Claude Code",
|
|
1152
|
+
targetDir: ".claude/skills"
|
|
1153
|
+
},
|
|
1154
|
+
{
|
|
1155
|
+
name: "opencode",
|
|
1156
|
+
label: "OpenCode",
|
|
1157
|
+
targetDir: ".opencode/skills"
|
|
1158
|
+
}
|
|
1159
|
+
];
|
|
1160
|
+
|
|
1161
|
+
// src/generators/init.ts
|
|
935
1162
|
import fs2 from "fs";
|
|
936
|
-
import os from "os";
|
|
937
1163
|
import path5 from "path";
|
|
1164
|
+
function resolveSkillsDir() {
|
|
1165
|
+
const entryDir = path5.dirname(process.argv[1] ?? "");
|
|
1166
|
+
return path5.resolve(entryDir, "skills");
|
|
1167
|
+
}
|
|
1168
|
+
function listFilesRecursive(dir, base) {
|
|
1169
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
1170
|
+
const files = [];
|
|
1171
|
+
for (const entry of entries) {
|
|
1172
|
+
const childBase = `${base}/${entry.name}`;
|
|
1173
|
+
const fullPath = path5.join(dir, entry.name);
|
|
1174
|
+
if (entry.isDirectory()) {
|
|
1175
|
+
files.push(...listFilesRecursive(fullPath, childBase));
|
|
1176
|
+
} else {
|
|
1177
|
+
files.push(childBase);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return files;
|
|
1181
|
+
}
|
|
1182
|
+
function generateInitSkills(targetBaseDir, cwd) {
|
|
1183
|
+
const skillsDir = resolveSkillsDir();
|
|
1184
|
+
if (!fs2.existsSync(skillsDir)) {
|
|
1185
|
+
logger.error(`Bundled skills directory not found: ${skillsDir}`);
|
|
1186
|
+
logger.error('Please run "lux build" or reinstall lux.');
|
|
1187
|
+
return { copiedFiles: [], targetDir: targetBaseDir };
|
|
1188
|
+
}
|
|
1189
|
+
const targetPath = path5.resolve(cwd, targetBaseDir);
|
|
1190
|
+
try {
|
|
1191
|
+
fs2.cpSync(skillsDir, targetPath, { recursive: true, force: true });
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1194
|
+
logger.error(`Failed to copy skills to ${targetPath}: ${message}`);
|
|
1195
|
+
return { copiedFiles: [], targetDir: targetBaseDir };
|
|
1196
|
+
}
|
|
1197
|
+
const copiedFiles = fs2.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
|
|
1198
|
+
return { copiedFiles, targetDir: targetBaseDir };
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// src/commands/init.ts
|
|
1202
|
+
function registerInitCommand(program2) {
|
|
1203
|
+
program2.command("init").description("Initialize AI coding tool skills in current project").action(async () => {
|
|
1204
|
+
const toolOptions = INIT_TOOLS.map((tool2) => ({
|
|
1205
|
+
value: tool2.name,
|
|
1206
|
+
label: tool2.label
|
|
1207
|
+
}));
|
|
1208
|
+
const selected = await select({
|
|
1209
|
+
message: "Which AI coding tool do you use?",
|
|
1210
|
+
options: toolOptions
|
|
1211
|
+
});
|
|
1212
|
+
if (isCancel(selected)) {
|
|
1213
|
+
cancel("Operation cancelled.");
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const tool = INIT_TOOLS.find((t) => t.name === selected);
|
|
1217
|
+
if (!tool) {
|
|
1218
|
+
logger.error(`Unknown tool: ${String(selected)}`);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
const cwd = process.cwd();
|
|
1222
|
+
const result = generateInitSkills(tool.targetDir, cwd);
|
|
1223
|
+
if (result.copiedFiles.length === 0) {
|
|
1224
|
+
logger.warn("No skill files were copied.");
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
for (const file of result.copiedFiles) {
|
|
1228
|
+
logger.log(` ${file}`);
|
|
1229
|
+
}
|
|
1230
|
+
outro(`Skills installed to ${tool.targetDir}/`);
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// src/utils/config.ts
|
|
1235
|
+
import fs3 from "fs";
|
|
1236
|
+
import os from "os";
|
|
1237
|
+
import path6 from "path";
|
|
938
1238
|
var CONFIG_DIR = ".lux";
|
|
939
1239
|
var ENV_FILE = "env.txt";
|
|
940
1240
|
function getEnvConfigPath() {
|
|
941
|
-
return
|
|
1241
|
+
return path6.join(os.homedir(), CONFIG_DIR, ENV_FILE);
|
|
942
1242
|
}
|
|
943
1243
|
function getEnvConfig() {
|
|
944
1244
|
let content;
|
|
945
1245
|
try {
|
|
946
|
-
content =
|
|
1246
|
+
content = fs3.readFileSync(getEnvConfigPath(), "utf-8");
|
|
947
1247
|
} catch {
|
|
948
1248
|
return {};
|
|
949
1249
|
}
|
|
@@ -966,7 +1266,7 @@ function setEnvConfig(data) {
|
|
|
966
1266
|
}
|
|
967
1267
|
function clearEnvConfig() {
|
|
968
1268
|
try {
|
|
969
|
-
|
|
1269
|
+
fs3.unlinkSync(getEnvConfigPath());
|
|
970
1270
|
} catch {
|
|
971
1271
|
}
|
|
972
1272
|
}
|
|
@@ -988,27 +1288,6 @@ function registerShowCommand(program2) {
|
|
|
988
1288
|
show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
|
|
989
1289
|
}
|
|
990
1290
|
|
|
991
|
-
// src/utils/execFileNoThrow.ts
|
|
992
|
-
import { exec } from "child_process";
|
|
993
|
-
import { promisify } from "util";
|
|
994
|
-
var execAsync = promisify(exec);
|
|
995
|
-
async function execFileNoThrow(command, args, options) {
|
|
996
|
-
const cmdStr = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
997
|
-
try {
|
|
998
|
-
const { stdout, stderr } = await execAsync(cmdStr, {
|
|
999
|
-
cwd: options?.cwd
|
|
1000
|
-
});
|
|
1001
|
-
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 };
|
|
1002
|
-
} catch (err) {
|
|
1003
|
-
const error = err;
|
|
1004
|
-
return {
|
|
1005
|
-
stdout: (error.stdout ?? "").trim(),
|
|
1006
|
-
stderr: (error.stderr ?? "").trim(),
|
|
1007
|
-
exitCode: error.code === "ENOENT" ? null : 1
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
1291
|
// src/utils/version.ts
|
|
1013
1292
|
import { existsSync, readFileSync } from "fs";
|
|
1014
1293
|
import { dirname, join } from "path";
|
|
@@ -1206,6 +1485,138 @@ var webVueVscode = {
|
|
|
1206
1485
|
]
|
|
1207
1486
|
};
|
|
1208
1487
|
|
|
1488
|
+
// src/presets/vscode/web-react.ts
|
|
1489
|
+
var webReactVscode = {
|
|
1490
|
+
name: "web-react",
|
|
1491
|
+
description: "VSCode config for React Web",
|
|
1492
|
+
settings: () => ({
|
|
1493
|
+
// ===== Editor Preferences =====
|
|
1494
|
+
"editor.tabSize": 2,
|
|
1495
|
+
"editor.detectIndentation": false,
|
|
1496
|
+
"editor.insertSpaces": true,
|
|
1497
|
+
"editor.renderWhitespace": "selection",
|
|
1498
|
+
"editor.guides.indentation": true,
|
|
1499
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1500
|
+
"editor.formatOnSave": true,
|
|
1501
|
+
"editor.codeActionsOnSave": {
|
|
1502
|
+
"source.fixAll.eslint": "explicit",
|
|
1503
|
+
"source.fixAll.stylelint": "explicit",
|
|
1504
|
+
"source.organizeImports": "never"
|
|
1505
|
+
},
|
|
1506
|
+
// Cursor & Animation
|
|
1507
|
+
"editor.cursorBlinking": "expand",
|
|
1508
|
+
"editor.cursorSmoothCaretAnimation": "on",
|
|
1509
|
+
"editor.largeFileOptimizations": true,
|
|
1510
|
+
// Code Assistance
|
|
1511
|
+
"editor.inlineSuggest.enabled": true,
|
|
1512
|
+
"editor.suggestSelection": "recentlyUsedByPrefix",
|
|
1513
|
+
"editor.acceptSuggestionOnEnter": "smart",
|
|
1514
|
+
"editor.bracketPairColorization.enabled": true,
|
|
1515
|
+
"editor.autoClosingBrackets": "beforeWhitespace",
|
|
1516
|
+
"editor.autoClosingOvertype": "always",
|
|
1517
|
+
// ===== TypeScript =====
|
|
1518
|
+
"js/ts.inlayHints.enumMemberValues.enabled": true,
|
|
1519
|
+
"js/ts.preferences.preferTypeOnlyAutoImports": true,
|
|
1520
|
+
"js/ts.preferences.includePackageJsonAutoImports": "on",
|
|
1521
|
+
"js/ts.preferences.importModuleSpecifier": "relative",
|
|
1522
|
+
"js/ts.suggest.autoImports": true,
|
|
1523
|
+
"js/ts.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
|
|
1524
|
+
// ===== Language-specific Formatting =====
|
|
1525
|
+
"[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
1526
|
+
"[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
1527
|
+
"[scss]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
1528
|
+
"[typescript]": {
|
|
1529
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1530
|
+
"editor.formatOnSave": true
|
|
1531
|
+
},
|
|
1532
|
+
"[javascript]": {
|
|
1533
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1534
|
+
"editor.formatOnSave": true
|
|
1535
|
+
},
|
|
1536
|
+
"[typescriptreact]": {
|
|
1537
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1538
|
+
"editor.formatOnSave": true
|
|
1539
|
+
},
|
|
1540
|
+
"[javascriptreact]": {
|
|
1541
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1542
|
+
"editor.formatOnSave": true
|
|
1543
|
+
},
|
|
1544
|
+
"[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
1545
|
+
"[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
1546
|
+
// ===== Terminal =====
|
|
1547
|
+
"terminal.integrated.cursorBlinking": true,
|
|
1548
|
+
"terminal.integrated.tabs.enabled": true,
|
|
1549
|
+
"terminal.integrated.scrollback": 1e4,
|
|
1550
|
+
// ===== File Exclusion =====
|
|
1551
|
+
"files.watcherExclude": {
|
|
1552
|
+
"**/.git/objects/**": true,
|
|
1553
|
+
"**/.git/subtree-cache/**": true,
|
|
1554
|
+
"**/.vscode/**": true,
|
|
1555
|
+
"**/node_modules/**": true,
|
|
1556
|
+
"**/tmp/**": true,
|
|
1557
|
+
"**/dist/**": true,
|
|
1558
|
+
"**/pnpm-lock.yaml": true,
|
|
1559
|
+
"**/package-lock.json": true,
|
|
1560
|
+
"**/bun.lock": true,
|
|
1561
|
+
"**/yarn.lock": true
|
|
1562
|
+
},
|
|
1563
|
+
"search.exclude": {
|
|
1564
|
+
"**/node_modules": true,
|
|
1565
|
+
"**/*.log": true,
|
|
1566
|
+
"**/*.log*": true,
|
|
1567
|
+
"**/dist": true,
|
|
1568
|
+
"**/.git": true,
|
|
1569
|
+
"**/.vscode": false,
|
|
1570
|
+
"**/tmp": true,
|
|
1571
|
+
node_modules: true,
|
|
1572
|
+
"**/pnpm-lock.yaml": true,
|
|
1573
|
+
"**/package-lock.json": true,
|
|
1574
|
+
"**/bun.lock": true,
|
|
1575
|
+
"**/yarn.lock": true
|
|
1576
|
+
},
|
|
1577
|
+
// ===== File Nesting =====
|
|
1578
|
+
"explorer.fileNesting.enabled": true,
|
|
1579
|
+
"explorer.fileNesting.expand": false,
|
|
1580
|
+
"explorer.fileNesting.patterns": {
|
|
1581
|
+
"package.json": "pnpm-lock.yaml,yarn.lock,bun.lock, .gitignore, .browserslistrc, .npmrc, cspell.json,README.md, LICENSE*,.editorconfig",
|
|
1582
|
+
"eslint.config.mjs": ".prettierignore, .prettierrc, .prettierrc.json, .editorconfig",
|
|
1583
|
+
"tsconfig.json": "tsconfig.*.json",
|
|
1584
|
+
"tailwind.config.js": "postcss.config.js",
|
|
1585
|
+
"vite.config.{js,ts}": "vite.*.{js,ts}",
|
|
1586
|
+
".env": ".env.*"
|
|
1587
|
+
},
|
|
1588
|
+
// ===== ESLint =====
|
|
1589
|
+
"eslint.validate": [
|
|
1590
|
+
"javascript",
|
|
1591
|
+
"typescript",
|
|
1592
|
+
"javascriptreact",
|
|
1593
|
+
"typescriptreact",
|
|
1594
|
+
"html",
|
|
1595
|
+
"markdown",
|
|
1596
|
+
"json",
|
|
1597
|
+
"jsonc",
|
|
1598
|
+
"json5"
|
|
1599
|
+
],
|
|
1600
|
+
// ===== Stylelint =====
|
|
1601
|
+
"stylelint.enable": true,
|
|
1602
|
+
"stylelint.validate": ["css", "scss"],
|
|
1603
|
+
"stylelint.snippet": ["css", "scss"],
|
|
1604
|
+
"css.validate": false,
|
|
1605
|
+
"less.validate": false,
|
|
1606
|
+
"scss.validate": false,
|
|
1607
|
+
// ===== CSpell =====
|
|
1608
|
+
"cSpell.language": "en"
|
|
1609
|
+
}),
|
|
1610
|
+
extensions: () => [
|
|
1611
|
+
"dbaeumer.vscode-eslint",
|
|
1612
|
+
"esbenp.prettier-vscode",
|
|
1613
|
+
"stylelint.vscode-stylelint",
|
|
1614
|
+
"mrmlnc.vscode-scss",
|
|
1615
|
+
"streetsidesoftware.code-spell-checker",
|
|
1616
|
+
"editorconfig.editorconfig"
|
|
1617
|
+
]
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1209
1620
|
// src/presets/vscode/electron.ts
|
|
1210
1621
|
var electronVueVscode = {
|
|
1211
1622
|
name: "electron-vue",
|
|
@@ -1697,6 +2108,7 @@ var goVscode = {
|
|
|
1697
2108
|
// src/presets/vscode/index.ts
|
|
1698
2109
|
var VSCODE_PRESETS = [
|
|
1699
2110
|
webVueVscode,
|
|
2111
|
+
webReactVscode,
|
|
1700
2112
|
electronVueVscode,
|
|
1701
2113
|
uniappVscode,
|
|
1702
2114
|
nodeVscode,
|
|
@@ -1769,20 +2181,45 @@ function generateVscodeSettings(preset, opts) {
|
|
|
1769
2181
|
if (existingSettings) {
|
|
1770
2182
|
const backupPath = `${settingsPath}.bak`;
|
|
1771
2183
|
if (!fileExists(backupPath)) {
|
|
1772
|
-
|
|
1773
|
-
|
|
2184
|
+
try {
|
|
2185
|
+
writeFile(backupPath, JSON.stringify(existingSettings, null, 2) + "\n");
|
|
2186
|
+
logger.log("Backed up .vscode/settings.json \u2192 settings.json.bak");
|
|
2187
|
+
} catch (error) {
|
|
2188
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2189
|
+
logger.warn(
|
|
2190
|
+
`Failed to backup .vscode/settings.json: ${message}. Continuing without backup.`
|
|
2191
|
+
);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
try {
|
|
2195
|
+
const merged = mergeVscodeSettings(presetSettings, existingSettings);
|
|
2196
|
+
writeJson(settingsPath, merged);
|
|
2197
|
+
} catch (error) {
|
|
2198
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2199
|
+
logger.error(`Failed to write .vscode/settings.json: ${msg}`);
|
|
2200
|
+
return null;
|
|
1774
2201
|
}
|
|
1775
|
-
const merged = mergeVscodeSettings(presetSettings, existingSettings);
|
|
1776
|
-
writeJson(settingsPath, merged);
|
|
1777
2202
|
return "overwritten";
|
|
1778
2203
|
}
|
|
1779
|
-
|
|
2204
|
+
try {
|
|
2205
|
+
writeJson(settingsPath, presetSettings);
|
|
2206
|
+
} catch (error) {
|
|
2207
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2208
|
+
logger.error(`Failed to write .vscode/settings.json: ${msg}`);
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
1780
2211
|
return "created";
|
|
1781
2212
|
}
|
|
1782
2213
|
function generateVscodeExtensions(preset, opts) {
|
|
1783
2214
|
if (opts.dryRun) return "created";
|
|
1784
2215
|
const extensions = opts.noStylelint ? preset.extensions().filter((ext) => ext !== STYLELINT_EXTENSION) : preset.extensions();
|
|
1785
|
-
|
|
2216
|
+
try {
|
|
2217
|
+
writeJson(`${opts.cwd}/.vscode/extensions.json`, { recommendations: extensions });
|
|
2218
|
+
} catch (error) {
|
|
2219
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2220
|
+
logger.error(`Failed to write .vscode/extensions.json: ${msg}`);
|
|
2221
|
+
return null;
|
|
2222
|
+
}
|
|
1786
2223
|
return "created";
|
|
1787
2224
|
}
|
|
1788
2225
|
function generateAllVscode(preset, opts) {
|
|
@@ -1811,7 +2248,7 @@ function filterStylelintSettings(settings) {
|
|
|
1811
2248
|
// src/commands/vscode.ts
|
|
1812
2249
|
function registerVscodeCommand(program2) {
|
|
1813
2250
|
const vscode = program2.command("vscode").description("Initialize VSCode config with preset");
|
|
1814
|
-
vscode.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--dry-run", "Preview without writing files").option("--
|
|
2251
|
+
vscode.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--dry-run", "Preview without writing files").option("--stylelint", "Include Stylelint settings and extension").action(
|
|
1815
2252
|
async (presetName, options) => {
|
|
1816
2253
|
const preset = resolvePreset(VSCODE_PRESETS, presetName);
|
|
1817
2254
|
if (!preset) return;
|
|
@@ -1820,7 +2257,8 @@ function registerVscodeCommand(program2) {
|
|
|
1820
2257
|
cwd,
|
|
1821
2258
|
force: options.force ?? false,
|
|
1822
2259
|
dryRun: options.dryRun ?? false,
|
|
1823
|
-
noStylelint: options.stylelint
|
|
2260
|
+
noStylelint: options.stylelint !== true,
|
|
2261
|
+
noEditorconfig: false
|
|
1824
2262
|
};
|
|
1825
2263
|
const result = generateAllVscode(preset, opts);
|
|
1826
2264
|
const files = [...result.created, ...result.overwritten];
|
|
@@ -1921,6 +2359,7 @@ function registerVpnCommand(program2) {
|
|
|
1921
2359
|
// src/index.ts
|
|
1922
2360
|
program.name("lux").description("One-click project formatting & VSCode config CLI").version(getCurrentVersion());
|
|
1923
2361
|
registerFmtCommand(program);
|
|
2362
|
+
registerInitCommand(program);
|
|
1924
2363
|
registerVscodeCommand(program);
|
|
1925
2364
|
registerVpnCommand(program);
|
|
1926
2365
|
registerShowCommand(program);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lux
|
|
3
|
+
description: Use when setting up ESLint, Prettier, CSpell, Stylelint, EditorConfig, VSCode workspace settings, proxy env
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## fmt — generate lint/format configs
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
lux fmt <preset> [--stylelint] [--editorconfig]
|
|
10
|
+
lux fmt list
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- `--force` — overwrite existing config files (default: skip)
|
|
14
|
+
- `--dry-run` — preview what would be generated, write nothing
|
|
15
|
+
- `--no-install` — write deps to package.json but skip install
|
|
16
|
+
|
|
17
|
+
Presets: `web-vue` `web-react` `electron-vue` `uniapp` `node` `nest`
|
|
18
|
+
|
|
19
|
+
## vscode — generate editor settings
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
lux vscode <preset> [--dry-run]
|
|
23
|
+
lux vscode list
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Presets: `web-vue` `web-react` `electron-vue` `uniapp` `node` `nest` `go`
|
|
27
|
+
|
|
28
|
+
## init — lux skill(human interaction)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
lux init
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## vpn — proxy clipboard helper
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
lux vpn cmd # copy CMD proxy commands
|
|
38
|
+
lux vpn pw # copy PowerShell proxy commands
|
|
39
|
+
lux vpn bash # copy Bash proxy commands
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## env — proxy env management
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
lux set https_proxy=http://127.0.0.1:7890
|
|
46
|
+
lux unset # clear all proxy config
|
|
47
|
+
lux show env # show stored proxy env
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## update — self-update
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
lux update # update to latest
|
|
54
|
+
lux update --check
|
|
55
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luxkit/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "One-click project formatting & VSCode config CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,27 +13,39 @@
|
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "tsup",
|
|
16
|
+
"build": "tsup && node scripts/copy-assets.mjs",
|
|
17
17
|
"dev": "tsup --watch",
|
|
18
18
|
"lint": "eslint .",
|
|
19
|
-
"lint:fix": "eslint \"src/**/*.{js,ts}\" --fix",
|
|
20
19
|
"format": "prettier --write \"src/**/*.{ts,js,json}\"",
|
|
21
|
-
"format:check": "prettier --check \"src/**/*.{ts,js,json}\"",
|
|
22
20
|
"cspell": "cspell --gitignore \"src/**/*\"",
|
|
21
|
+
"format:check": "prettier --check \"src/**/*.{ts,js,json}\"",
|
|
23
22
|
"type:check": "tsc --noEmit",
|
|
24
23
|
"code:check": "bun run lint && bun run format:check",
|
|
25
|
-
"code:fix": "bun run lint:fix && bun run format",
|
|
26
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
27
|
"code:fix:all": "bun run lint:fix && bun run format",
|
|
28
28
|
"prepublishOnly": "cross-env NODE_ENV=production bun run build",
|
|
29
29
|
"test": "vitest run",
|
|
30
30
|
"test:watch": "vitest",
|
|
31
31
|
"test:coverage": "vitest run --coverage"
|
|
32
32
|
},
|
|
33
|
-
"keywords": [
|
|
34
|
-
|
|
33
|
+
"keywords": [
|
|
34
|
+
"cli",
|
|
35
|
+
"eslint",
|
|
36
|
+
"prettier",
|
|
37
|
+
"stylelint",
|
|
38
|
+
"cspell",
|
|
39
|
+
"editorconfig",
|
|
40
|
+
"vscode",
|
|
41
|
+
"formatting",
|
|
42
|
+
"config-generator",
|
|
43
|
+
"preset"
|
|
44
|
+
],
|
|
45
|
+
"author": "TTT1231",
|
|
35
46
|
"license": "ISC",
|
|
36
47
|
"dependencies": {
|
|
48
|
+
"@clack/prompts": "^1.4.0",
|
|
37
49
|
"chalk": "5",
|
|
38
50
|
"commander": "^14.0.3"
|
|
39
51
|
},
|