@luxkit/cli 1.0.7 → 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 +508 -54
- 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"));
|
|
@@ -781,9 +980,25 @@ async function installDevDeps(packages, cwd, pm) {
|
|
|
781
980
|
}
|
|
782
981
|
|
|
783
982
|
// src/commands/fmt.ts
|
|
983
|
+
function filterStylelintScripts(scripts) {
|
|
984
|
+
const filtered = {};
|
|
985
|
+
for (const [key, value] of Object.entries(scripts)) {
|
|
986
|
+
if (key.startsWith("stylelint")) continue;
|
|
987
|
+
filtered[key] = value.replace(/\s*&&\s*<pm>\s+stylelint\S*/g, "");
|
|
988
|
+
}
|
|
989
|
+
return filtered;
|
|
990
|
+
}
|
|
991
|
+
function isNotStylelintDep(dep) {
|
|
992
|
+
if (dep.includes("stylelint")) return false;
|
|
993
|
+
if (dep === "postcss-html" || dep === "postcss-scss") return false;
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
function isNotEditorconfigDep(dep) {
|
|
997
|
+
return !dep.includes("editorconfig");
|
|
998
|
+
}
|
|
784
999
|
function registerFmtCommand(program2) {
|
|
785
1000
|
const fmt = program2.command("fmt").description("Initialize formatting config with preset");
|
|
786
|
-
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(
|
|
787
1002
|
async (presetName, options) => {
|
|
788
1003
|
const preset = resolvePreset(FMT_PRESETS, presetName);
|
|
789
1004
|
if (!preset) return;
|
|
@@ -793,7 +1008,8 @@ function registerFmtCommand(program2) {
|
|
|
793
1008
|
cwd,
|
|
794
1009
|
force: options.force ?? false,
|
|
795
1010
|
dryRun: options.dryRun ?? false,
|
|
796
|
-
noStylelint: options.stylelint
|
|
1011
|
+
noStylelint: options.stylelint !== true,
|
|
1012
|
+
noEditorconfig: options.editorconfig !== true,
|
|
797
1013
|
lockfile: pm ? getLockfileName(pm) : void 0
|
|
798
1014
|
};
|
|
799
1015
|
const result = generateAllFmt(preset, opts);
|
|
@@ -807,24 +1023,33 @@ function registerFmtCommand(program2) {
|
|
|
807
1023
|
warnMissingPackageJson(preset, options.install !== false);
|
|
808
1024
|
return;
|
|
809
1025
|
}
|
|
810
|
-
|
|
811
|
-
|
|
1026
|
+
const scripts = opts.noStylelint && preset.scripts ? filterStylelintScripts(preset.scripts) : preset.scripts;
|
|
1027
|
+
if (scripts) {
|
|
1028
|
+
await injectScripts(scripts, opts, pm);
|
|
812
1029
|
}
|
|
813
1030
|
if (!preset.dependencies?.dev) return;
|
|
1031
|
+
const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep) : preset.dependencies.dev;
|
|
1032
|
+
const finalDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep) : devDeps;
|
|
814
1033
|
if (options.install === false) {
|
|
815
|
-
|
|
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
|
+
}
|
|
816
1040
|
return;
|
|
817
1041
|
}
|
|
818
1042
|
if (opts.dryRun) {
|
|
819
|
-
logger.log(`[dry-run] Would install: ${
|
|
1043
|
+
logger.log(`[dry-run] Would install: ${finalDeps.join(", ")}`);
|
|
820
1044
|
return;
|
|
821
1045
|
}
|
|
822
1046
|
try {
|
|
823
1047
|
logger.log(`Installing dependencies with ${pm}...`);
|
|
824
|
-
await installDevDeps(
|
|
1048
|
+
await installDevDeps(finalDeps, cwd, pm);
|
|
825
1049
|
logger.success("Dependencies installed successfully");
|
|
826
|
-
} catch {
|
|
827
|
-
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1052
|
+
logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
|
|
828
1053
|
}
|
|
829
1054
|
}
|
|
830
1055
|
);
|
|
@@ -916,19 +1141,109 @@ async function injectScripts(scripts, opts, pm) {
|
|
|
916
1141
|
}
|
|
917
1142
|
}
|
|
918
1143
|
|
|
919
|
-
// 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
|
|
920
1162
|
import fs2 from "fs";
|
|
921
|
-
import os from "os";
|
|
922
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";
|
|
923
1238
|
var CONFIG_DIR = ".lux";
|
|
924
1239
|
var ENV_FILE = "env.txt";
|
|
925
1240
|
function getEnvConfigPath() {
|
|
926
|
-
return
|
|
1241
|
+
return path6.join(os.homedir(), CONFIG_DIR, ENV_FILE);
|
|
927
1242
|
}
|
|
928
1243
|
function getEnvConfig() {
|
|
929
1244
|
let content;
|
|
930
1245
|
try {
|
|
931
|
-
content =
|
|
1246
|
+
content = fs3.readFileSync(getEnvConfigPath(), "utf-8");
|
|
932
1247
|
} catch {
|
|
933
1248
|
return {};
|
|
934
1249
|
}
|
|
@@ -951,7 +1266,7 @@ function setEnvConfig(data) {
|
|
|
951
1266
|
}
|
|
952
1267
|
function clearEnvConfig() {
|
|
953
1268
|
try {
|
|
954
|
-
|
|
1269
|
+
fs3.unlinkSync(getEnvConfigPath());
|
|
955
1270
|
} catch {
|
|
956
1271
|
}
|
|
957
1272
|
}
|
|
@@ -973,27 +1288,6 @@ function registerShowCommand(program2) {
|
|
|
973
1288
|
show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
|
|
974
1289
|
}
|
|
975
1290
|
|
|
976
|
-
// src/utils/execFileNoThrow.ts
|
|
977
|
-
import { exec } from "child_process";
|
|
978
|
-
import { promisify } from "util";
|
|
979
|
-
var execAsync = promisify(exec);
|
|
980
|
-
async function execFileNoThrow(command, args, options) {
|
|
981
|
-
const cmdStr = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
982
|
-
try {
|
|
983
|
-
const { stdout, stderr } = await execAsync(cmdStr, {
|
|
984
|
-
cwd: options?.cwd
|
|
985
|
-
});
|
|
986
|
-
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 };
|
|
987
|
-
} catch (err) {
|
|
988
|
-
const error = err;
|
|
989
|
-
return {
|
|
990
|
-
stdout: (error.stdout ?? "").trim(),
|
|
991
|
-
stderr: (error.stderr ?? "").trim(),
|
|
992
|
-
exitCode: error.code === "ENOENT" ? null : 1
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
|
|
997
1291
|
// src/utils/version.ts
|
|
998
1292
|
import { existsSync, readFileSync } from "fs";
|
|
999
1293
|
import { dirname, join } from "path";
|
|
@@ -1191,6 +1485,138 @@ var webVueVscode = {
|
|
|
1191
1485
|
]
|
|
1192
1486
|
};
|
|
1193
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
|
+
|
|
1194
1620
|
// src/presets/vscode/electron.ts
|
|
1195
1621
|
var electronVueVscode = {
|
|
1196
1622
|
name: "electron-vue",
|
|
@@ -1682,6 +2108,7 @@ var goVscode = {
|
|
|
1682
2108
|
// src/presets/vscode/index.ts
|
|
1683
2109
|
var VSCODE_PRESETS = [
|
|
1684
2110
|
webVueVscode,
|
|
2111
|
+
webReactVscode,
|
|
1685
2112
|
electronVueVscode,
|
|
1686
2113
|
uniappVscode,
|
|
1687
2114
|
nodeVscode,
|
|
@@ -1754,20 +2181,45 @@ function generateVscodeSettings(preset, opts) {
|
|
|
1754
2181
|
if (existingSettings) {
|
|
1755
2182
|
const backupPath = `${settingsPath}.bak`;
|
|
1756
2183
|
if (!fileExists(backupPath)) {
|
|
1757
|
-
|
|
1758
|
-
|
|
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;
|
|
1759
2201
|
}
|
|
1760
|
-
const merged = mergeVscodeSettings(presetSettings, existingSettings);
|
|
1761
|
-
writeJson(settingsPath, merged);
|
|
1762
2202
|
return "overwritten";
|
|
1763
2203
|
}
|
|
1764
|
-
|
|
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
|
+
}
|
|
1765
2211
|
return "created";
|
|
1766
2212
|
}
|
|
1767
2213
|
function generateVscodeExtensions(preset, opts) {
|
|
1768
2214
|
if (opts.dryRun) return "created";
|
|
1769
2215
|
const extensions = opts.noStylelint ? preset.extensions().filter((ext) => ext !== STYLELINT_EXTENSION) : preset.extensions();
|
|
1770
|
-
|
|
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
|
+
}
|
|
1771
2223
|
return "created";
|
|
1772
2224
|
}
|
|
1773
2225
|
function generateAllVscode(preset, opts) {
|
|
@@ -1796,7 +2248,7 @@ function filterStylelintSettings(settings) {
|
|
|
1796
2248
|
// src/commands/vscode.ts
|
|
1797
2249
|
function registerVscodeCommand(program2) {
|
|
1798
2250
|
const vscode = program2.command("vscode").description("Initialize VSCode config with preset");
|
|
1799
|
-
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(
|
|
1800
2252
|
async (presetName, options) => {
|
|
1801
2253
|
const preset = resolvePreset(VSCODE_PRESETS, presetName);
|
|
1802
2254
|
if (!preset) return;
|
|
@@ -1805,7 +2257,8 @@ function registerVscodeCommand(program2) {
|
|
|
1805
2257
|
cwd,
|
|
1806
2258
|
force: options.force ?? false,
|
|
1807
2259
|
dryRun: options.dryRun ?? false,
|
|
1808
|
-
noStylelint: options.stylelint
|
|
2260
|
+
noStylelint: options.stylelint !== true,
|
|
2261
|
+
noEditorconfig: false
|
|
1809
2262
|
};
|
|
1810
2263
|
const result = generateAllVscode(preset, opts);
|
|
1811
2264
|
const files = [...result.created, ...result.overwritten];
|
|
@@ -1906,6 +2359,7 @@ function registerVpnCommand(program2) {
|
|
|
1906
2359
|
// src/index.ts
|
|
1907
2360
|
program.name("lux").description("One-click project formatting & VSCode config CLI").version(getCurrentVersion());
|
|
1908
2361
|
registerFmtCommand(program);
|
|
2362
|
+
registerInitCommand(program);
|
|
1909
2363
|
registerVscodeCommand(program);
|
|
1910
2364
|
registerVpnCommand(program);
|
|
1911
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
|
},
|