@luxkit/cli 1.1.0 → 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/dist/index.js CHANGED
@@ -4,20 +4,21 @@
4
4
  import { program } from "commander";
5
5
 
6
6
  // src/commands/fmt.ts
7
- import path4 from "path";
7
+ import fs3 from "fs";
8
+ import path5 from "path";
8
9
 
9
10
  // src/presets/fmt/electron-vue.ts
10
11
  var electronVueFmt = {
11
12
  name: "electron-vue",
12
13
  description: "Vue 3 + Electron desktop app",
13
14
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
14
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
15
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
15
16
  import pluginVue from 'eslint-plugin-vue'
16
17
 
17
18
  export default [
18
19
  ...pluginVue.configs['flat/recommended'],
19
20
  ...withVue(),
20
- ...withPrettier(),
21
+ prettierConfig,
21
22
  {
22
23
  rules: {
23
24
  'vue/multi-word-component-names': 'off',
@@ -78,7 +79,8 @@ out/
78
79
  version: "0.2",
79
80
  language: "en,en-US",
80
81
  allowCompoundWords: true,
81
- words: ["vite", "pinia", "vueuse", "unplugin", "electron", "electron-builder"]
82
+ words: ["vite", "pinia", "vueuse", "unplugin", "electron", "electron-builder"],
83
+ ignorePaths: ["**/*.svg", "**/*.png"]
82
84
  },
83
85
  null,
84
86
  2
@@ -114,18 +116,9 @@ trim_trailing_whitespace = false
114
116
  ]
115
117
  },
116
118
  scripts: {
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
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
120
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
121
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
122
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
123
- cspell: 'cspell --gitignore "src/**/*"',
124
- "type:check": "vue-tsc --noEmit",
125
- "code:check": "<pm> lint && <pm> format:check",
126
- "code:fix": "<pm> lint:fix && <pm> format",
127
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
128
- "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}"'
129
122
  }
130
123
  };
131
124
 
@@ -162,7 +155,8 @@ coverage/
162
155
  version: "0.2",
163
156
  language: "en,en-US",
164
157
  allowCompoundWords: true,
165
- words: ["nestjs", "typeorm", "dtos"]
158
+ words: ["nestjs", "typeorm", "dtos"],
159
+ ignorePaths: ["**/*.svg", "**/*.png"]
166
160
  },
167
161
  null,
168
162
  2
@@ -183,12 +177,10 @@ trim_trailing_whitespace = false
183
177
  dependencies: {
184
178
  dev: ["prettier", "cspell"]
185
179
  },
186
- // NestJS: only append new scripts, don't conflict with existing ones
187
180
  scripts: {
188
- cspell: 'cspell --gitignore "src/**/*"',
189
- "type:check": "tsc --noEmit",
190
- "code:check": "<pm> lint && <pm> format:check",
191
- "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}"'
192
184
  }
193
185
  };
194
186
 
@@ -267,7 +259,8 @@ coverage/
267
259
  version: "0.2",
268
260
  language: "en,en-US",
269
261
  allowCompoundWords: true,
270
- words: []
262
+ words: [],
263
+ ignorePaths: ["**/*.svg", "**/*.png"]
271
264
  },
272
265
  null,
273
266
  2
@@ -297,16 +290,9 @@ trim_trailing_whitespace = false
297
290
  ]
298
291
  },
299
292
  scripts: {
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
- format: 'prettier --write "src/**/*.{ts,js,json}"',
303
- "format:check": 'prettier --check "src/**/*.{ts,js,json}"',
304
- cspell: 'cspell --gitignore "src/**/*"',
305
- "type:check": "tsc --noEmit",
306
- "code:check": "<pm> lint && <pm> format:check",
307
- "code:fix": "<pm> lint:fix && <pm> format",
308
- "code:check:all": "<pm> lint && <pm> format:check && <pm> cspell",
309
- "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}"'
310
296
  }
311
297
  };
312
298
 
@@ -315,13 +301,13 @@ var uniappFmt = {
315
301
  name: "uniapp",
316
302
  description: "Vue 3 + UniApp WeChat mini program",
317
303
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
318
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
304
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
319
305
  import pluginVue from 'eslint-plugin-vue'
320
306
 
321
307
  export default [
322
308
  ...pluginVue.configs['flat/recommended'],
323
309
  ...withVue(),
324
- ...withPrettier(),
310
+ prettierConfig,
325
311
  {
326
312
  rules: {
327
313
  'vue/multi-word-component-names': 'off',
@@ -380,7 +366,8 @@ unpackage/
380
366
  version: "0.2",
381
367
  language: "en,en-US",
382
368
  allowCompoundWords: true,
383
- words: ["vite", "pinia", "vueuse", "unplugin", "uniapp"]
369
+ words: ["vite", "pinia", "vueuse", "unplugin", "uniapp"],
370
+ ignorePaths: ["**/*.svg", "**/*.png"]
384
371
  },
385
372
  null,
386
373
  2
@@ -416,18 +403,9 @@ trim_trailing_whitespace = false
416
403
  ]
417
404
  },
418
405
  scripts: {
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
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
422
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
423
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
424
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
425
- cspell: 'cspell --gitignore "src/**/*"',
426
- "type:check": "vue-tsc --noEmit",
427
- "code:check": "<pm> lint && <pm> format:check",
428
- "code:fix": "<pm> lint:fix && <pm> format",
429
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
430
- "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}"'
431
409
  }
432
410
  };
433
411
 
@@ -506,7 +484,8 @@ dist/
506
484
  version: "0.2",
507
485
  language: "en,en-US",
508
486
  allowCompoundWords: true,
509
- words: ["vite", "react", "zustand", "tanstack"]
487
+ words: ["vite", "react", "zustand", "tanstack"],
488
+ ignorePaths: ["**/*.svg", "**/*.png"]
510
489
  },
511
490
  null,
512
491
  2
@@ -544,18 +523,9 @@ trim_trailing_whitespace = false
544
523
  ]
545
524
  },
546
525
  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"
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}"'
559
529
  }
560
530
  };
561
531
 
@@ -564,13 +534,13 @@ var webVueFmt = {
564
534
  name: "web-vue",
565
535
  description: "Vue 3 Web frontend (Vite + Vue + TypeScript)",
566
536
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
567
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
537
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
568
538
  import pluginVue from 'eslint-plugin-vue'
569
539
 
570
540
  export default [
571
541
  ...pluginVue.configs['flat/recommended'],
572
542
  ...withVue(),
573
- ...withPrettier(),
543
+ prettierConfig,
574
544
  {
575
545
  rules: {
576
546
  'vue/multi-word-component-names': 'off',
@@ -627,7 +597,8 @@ dist/
627
597
  version: "0.2",
628
598
  language: "en,en-US",
629
599
  allowCompoundWords: true,
630
- words: ["vite", "pinia", "vueuse", "unplugin"]
600
+ words: ["vite", "pinia", "vueuse", "unplugin"],
601
+ ignorePaths: ["**/*.svg", "**/*.png"]
631
602
  },
632
603
  null,
633
604
  2
@@ -663,18 +634,9 @@ trim_trailing_whitespace = false
663
634
  ]
664
635
  },
665
636
  scripts: {
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',
668
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
669
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
670
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
671
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
672
- cspell: 'cspell --gitignore "src/**/*"',
673
- "type:check": "vue-tsc --noEmit",
674
- "code:check": "<pm> lint && <pm> format:check",
675
- "code:fix": "<pm> lint:fix && <pm> format",
676
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
677
- "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}"'
678
640
  }
679
641
  };
680
642
 
@@ -979,12 +941,365 @@ async function installDevDeps(packages, cwd, pm) {
979
941
  }
980
942
  }
981
943
 
944
+ // src/core/local-preset.ts
945
+ import fs2 from "fs";
946
+ import os from "os";
947
+ import path4 from "path";
948
+
949
+ // src/core/merge-settings.ts
950
+ var USER_PRIORITY_KEYS = /* @__PURE__ */ new Set([
951
+ // Cursor/animation
952
+ "editor.cursorBlinking",
953
+ "editor.cursorSmoothCaretAnimation",
954
+ "editor.renderWhitespace",
955
+ "editor.guides.indentation",
956
+ "editor.largeFileOptimizations",
957
+ // Theme/appearance
958
+ "workbench.iconTheme",
959
+ "workbench.colorTheme",
960
+ // Suggestions
961
+ "editor.inlineSuggest.enabled",
962
+ "editor.suggestSelection",
963
+ "editor.acceptSuggestionOnEnter",
964
+ "editor.bracketPairColorization.enabled",
965
+ "editor.autoClosingBrackets",
966
+ "editor.autoClosingOvertype"
967
+ ]);
968
+ function mergeVscodeSettings(preset, existing) {
969
+ const result = { ...existing };
970
+ for (const [key, presetVal] of Object.entries(preset)) {
971
+ const existingVal = existing[key];
972
+ if (existingVal === void 0) {
973
+ result[key] = presetVal;
974
+ continue;
975
+ }
976
+ if (USER_PRIORITY_KEYS.has(key)) {
977
+ continue;
978
+ }
979
+ if (isPlainObject(presetVal) && isPlainObject(existingVal)) {
980
+ result[key] = mergeVscodeSettings(
981
+ presetVal,
982
+ existingVal
983
+ );
984
+ continue;
985
+ }
986
+ result[key] = presetVal;
987
+ }
988
+ return result;
989
+ }
990
+ function isPlainObject(val) {
991
+ return typeof val === "object" && val !== null && !Array.isArray(val);
992
+ }
993
+
994
+ // src/core/local-preset.ts
995
+ var CONFIG_GETTERS = [
996
+ { filename: "eslint.config.mjs", getContent: (p) => p.eslint?.() },
997
+ { filename: ".prettierrc", getContent: (p) => p.prettier?.() },
998
+ { filename: ".prettierignore", getContent: (p) => p.prettierIgnore?.() },
999
+ { filename: "stylelint.config.mjs", getContent: (p) => p.stylelint?.() },
1000
+ { filename: ".stylelintignore", getContent: (p) => p.stylelintIgnore?.() },
1001
+ { filename: "cspell.json", getContent: (p) => p.cspell?.() },
1002
+ { filename: ".editorconfig", getContent: (p) => p.editorconfig?.() }
1003
+ ];
1004
+ var STYLELINT_FILES = /* @__PURE__ */ new Set(["stylelint.config.mjs", ".stylelintignore"]);
1005
+ var EDITORCONFIG_FILE = ".editorconfig";
1006
+ var STYLELINT_SETTINGS_PREFIXES = [
1007
+ "stylelint.",
1008
+ "css.validate",
1009
+ "less.validate",
1010
+ "scss.validate"
1011
+ ];
1012
+ var STYLELINT_DEPS = /* @__PURE__ */ new Set([
1013
+ "stylelint",
1014
+ "stylelint-config-standard-scss",
1015
+ "stylelint-order",
1016
+ "stylelint-scss",
1017
+ "@stylistic/stylelint-plugin",
1018
+ "postcss-html",
1019
+ "postcss-scss"
1020
+ ]);
1021
+ var STYLELINT_EXTENSION = "stylelint.vscode-stylelint";
1022
+ function getLuxDir() {
1023
+ return process.env.LUX_HOME || path4.join(os.homedir(), ".lux");
1024
+ }
1025
+ function getLocalPresetDir(type, presetName) {
1026
+ if (!isValidPresetName(presetName)) {
1027
+ throw new Error(`Invalid preset name: "${presetName}"`);
1028
+ }
1029
+ return path4.join(getLuxDir(), "preset", type, presetName);
1030
+ }
1031
+ function isValidPresetName(name) {
1032
+ return name.length > 0 && !name.includes("/") && !name.includes("\\") && !name.includes("..");
1033
+ }
1034
+ function localPresetExists(type, presetName) {
1035
+ const dir = getLocalPresetDir(type, presetName);
1036
+ return fs2.existsSync(dir);
1037
+ }
1038
+ function resetLocalPreset(type, presetName) {
1039
+ const dir = getLocalPresetDir(type, presetName);
1040
+ if (fs2.existsSync(dir)) {
1041
+ fs2.rmSync(dir, { recursive: true, force: true });
1042
+ logger.log(`Reset local preset: ${dir}`);
1043
+ }
1044
+ }
1045
+ function materializeFmtPreset(presetName, preset, opts) {
1046
+ if (opts.dryRun) {
1047
+ logger.log("[dry-run] Would materialize local preset to ~/.lux/preset/fmt/" + presetName);
1048
+ return;
1049
+ }
1050
+ const presetDir = getLocalPresetDir("fmt", presetName);
1051
+ ensureDir(presetDir);
1052
+ for (const { filename, getContent } of CONFIG_GETTERS) {
1053
+ const content = getContent(preset);
1054
+ if (content === void 0) continue;
1055
+ const resolved = opts.lockfile ? content.replace(/<lockfile>/g, opts.lockfile) : content.replace(/<lockfile>\n?/g, "");
1056
+ writeFile(path4.join(presetDir, filename), resolved);
1057
+ }
1058
+ const templatePkg = buildTemplatePackageJson(preset);
1059
+ writeJson(path4.join(presetDir, "package.json"), templatePkg);
1060
+ logger.log(`Local preset created at ${presetDir}`);
1061
+ }
1062
+ function materializeVscodePreset(cwd, presetName) {
1063
+ const presetDir = getLocalPresetDir("vscode", presetName);
1064
+ ensureDir(presetDir);
1065
+ const settingsSrc = path4.join(cwd, ".vscode", "settings.json");
1066
+ if (fileExists(settingsSrc)) {
1067
+ const content = fs2.readFileSync(settingsSrc, "utf-8");
1068
+ writeFile(path4.join(presetDir, "settings.json"), content);
1069
+ }
1070
+ const extensionsSrc = path4.join(cwd, ".vscode", "extensions.json");
1071
+ if (fileExists(extensionsSrc)) {
1072
+ const content = fs2.readFileSync(extensionsSrc, "utf-8");
1073
+ writeFile(path4.join(presetDir, "extensions.json"), content);
1074
+ }
1075
+ logger.log(`Local preset created at ${presetDir}`);
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
+ }
1086
+ var InvalidPackageJsonError = class extends Error {
1087
+ constructor(filePath) {
1088
+ super(`package.json exists but is not valid JSON: ${filePath}`);
1089
+ this.filePath = filePath;
1090
+ }
1091
+ filePath;
1092
+ };
1093
+ function applyLocalFmtPreset(cwd, presetName, opts) {
1094
+ const result = {
1095
+ created: [],
1096
+ overwritten: [],
1097
+ skipped: [],
1098
+ scriptsAdded: 0,
1099
+ scriptsSkipped: 0
1100
+ };
1101
+ const presetDir = getLocalPresetDir("fmt", presetName);
1102
+ if (!fs2.existsSync(presetDir)) {
1103
+ logger.warn(`Local preset not found at ${presetDir}`);
1104
+ return result;
1105
+ }
1106
+ const projectPkgPath = path4.join(cwd, "package.json");
1107
+ if (fileExists(projectPkgPath)) {
1108
+ try {
1109
+ JSON.parse(fs2.readFileSync(projectPkgPath, "utf-8"));
1110
+ } catch {
1111
+ throw new InvalidPackageJsonError(projectPkgPath);
1112
+ }
1113
+ }
1114
+ const entries = fs2.readdirSync(presetDir).filter((name) => name !== "package.json" && fs2.statSync(path4.join(presetDir, name)).isFile());
1115
+ for (const filename of entries) {
1116
+ if (opts.noStylelint && STYLELINT_FILES.has(filename)) continue;
1117
+ if (opts.noEditorconfig && filename === EDITORCONFIG_FILE) continue;
1118
+ const destPath = path4.join(cwd, filename);
1119
+ const exists = fileExists(destPath);
1120
+ if (exists && !opts.force) {
1121
+ result.skipped.push(filename);
1122
+ if (opts.dryRun) {
1123
+ logger.log(`[dry-run] Skipped ${filename} (already exists)`);
1124
+ }
1125
+ continue;
1126
+ }
1127
+ if (opts.dryRun) {
1128
+ (exists ? result.overwritten : result.created).push(filename);
1129
+ logger.log(`[dry-run] Would copy ${filename} from local preset`);
1130
+ continue;
1131
+ }
1132
+ const content = fs2.readFileSync(path4.join(presetDir, filename), "utf-8");
1133
+ writeFile(destPath, content);
1134
+ (exists ? result.overwritten : result.created).push(filename);
1135
+ }
1136
+ const templatePkg = readJson(path4.join(presetDir, "package.json"));
1137
+ const projectPkg = readJson(projectPkgPath);
1138
+ if (templatePkg && projectPkg) {
1139
+ const pm = fileExists(path4.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1140
+ const merged = mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result);
1141
+ if (!opts.dryRun) {
1142
+ writeJson(projectPkgPath, merged);
1143
+ }
1144
+ }
1145
+ return result;
1146
+ }
1147
+ function applyLocalVscodePreset(cwd, presetName, opts) {
1148
+ const result = {
1149
+ created: [],
1150
+ overwritten: [],
1151
+ skipped: [],
1152
+ scriptsAdded: 0,
1153
+ scriptsSkipped: 0
1154
+ };
1155
+ const presetDir = getLocalPresetDir("vscode", presetName);
1156
+ if (!fs2.existsSync(presetDir)) {
1157
+ logger.warn(`Local preset not found at ${presetDir}`);
1158
+ return result;
1159
+ }
1160
+ const settingsSrc = path4.join(presetDir, "settings.json");
1161
+ if (fileExists(settingsSrc)) {
1162
+ const presetSettings = readJson(settingsSrc);
1163
+ const filteredSettings = opts.noStylelint ? filterStylelintSettings(presetSettings ?? {}) : presetSettings;
1164
+ if (filteredSettings) {
1165
+ const settingsDest = path4.join(cwd, ".vscode", "settings.json");
1166
+ const existingSettings = readJson(settingsDest);
1167
+ if (existingSettings) {
1168
+ if (opts.dryRun) {
1169
+ result.overwritten.push(".vscode/settings.json");
1170
+ logger.log("[dry-run] Would merge .vscode/settings.json from local preset");
1171
+ } else {
1172
+ const merged = mergeVscodeSettings(filteredSettings, existingSettings);
1173
+ writeJson(settingsDest, merged);
1174
+ result.overwritten.push(".vscode/settings.json");
1175
+ }
1176
+ } else {
1177
+ if (opts.dryRun) {
1178
+ result.created.push(".vscode/settings.json");
1179
+ logger.log("[dry-run] Would create .vscode/settings.json from local preset");
1180
+ } else {
1181
+ writeJson(settingsDest, filteredSettings);
1182
+ result.created.push(".vscode/settings.json");
1183
+ }
1184
+ }
1185
+ }
1186
+ }
1187
+ const extensionsSrc = path4.join(presetDir, "extensions.json");
1188
+ if (fileExists(extensionsSrc)) {
1189
+ const extensionsData = readJson(extensionsSrc);
1190
+ if (extensionsData) {
1191
+ let presetRecommendations = extensionsData.recommendations ?? [];
1192
+ if (opts.noStylelint) {
1193
+ presetRecommendations = presetRecommendations.filter(
1194
+ (ext) => ext !== STYLELINT_EXTENSION
1195
+ );
1196
+ }
1197
+ if (opts.dryRun) {
1198
+ result.created.push(".vscode/extensions.json");
1199
+ logger.log("[dry-run] Would create .vscode/extensions.json from local preset");
1200
+ } else {
1201
+ const extensionsDest = path4.join(cwd, ".vscode", "extensions.json");
1202
+ const existingExtensions = readJson(extensionsDest);
1203
+ const existingRecommendations = existingExtensions?.recommendations ?? [];
1204
+ const merged = [.../* @__PURE__ */ new Set([...existingRecommendations, ...presetRecommendations])];
1205
+ writeJson(extensionsDest, { recommendations: merged });
1206
+ result.created.push(".vscode/extensions.json");
1207
+ }
1208
+ }
1209
+ }
1210
+ return result;
1211
+ }
1212
+ function buildTemplatePackageJson(preset) {
1213
+ const deps = {};
1214
+ if (preset.dependencies?.dev) {
1215
+ for (const dep of preset.dependencies.dev) {
1216
+ deps[dep] = "<latest>";
1217
+ }
1218
+ }
1219
+ const scripts = preset.scripts ? { ...preset.scripts } : void 0;
1220
+ const result = {};
1221
+ if (Object.keys(deps).length > 0) {
1222
+ result.devDependencies = deps;
1223
+ }
1224
+ if (scripts && Object.keys(scripts).length > 0) {
1225
+ result.scripts = scripts;
1226
+ }
1227
+ return result;
1228
+ }
1229
+ function mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result) {
1230
+ const merged = { ...projectPkg };
1231
+ const prefix = pm ? getRunPrefix(pm) : "";
1232
+ if (templatePkg.devDependencies) {
1233
+ const existingDeps = merged.devDependencies ?? {};
1234
+ const newDeps = { ...existingDeps };
1235
+ for (const [dep, version] of Object.entries(templatePkg.devDependencies)) {
1236
+ if (opts.noStylelint && STYLELINT_DEPS.has(dep)) continue;
1237
+ if (opts.noEditorconfig && dep.includes("editorconfig")) continue;
1238
+ if (existingDeps[dep] === void 0 && version !== "<latest>") {
1239
+ newDeps[dep] = version;
1240
+ }
1241
+ }
1242
+ merged.devDependencies = newDeps;
1243
+ }
1244
+ if (templatePkg.scripts) {
1245
+ const existingScripts = merged.scripts ?? {};
1246
+ const newScripts = { ...existingScripts };
1247
+ for (const [key, value] of Object.entries(templatePkg.scripts)) {
1248
+ let resolved = value.replace(/<pm>/g, prefix);
1249
+ if (opts.noStylelint) {
1250
+ resolved = resolved.replace(/\s*&&\s*stylelint\s+"[^"]*".*/g, "");
1251
+ }
1252
+ if (existingScripts[key] !== void 0 && !opts.force) {
1253
+ result.scriptsSkipped++;
1254
+ if (opts.dryRun) {
1255
+ logger.log(`[dry-run] Skipped script "${key}" (already exists)`);
1256
+ } else {
1257
+ logger.log(`Skipped script "${key}" (already exists)`);
1258
+ }
1259
+ continue;
1260
+ }
1261
+ if (opts.dryRun) {
1262
+ result.scriptsAdded++;
1263
+ logger.log(`[dry-run] Would add script "${key}"`);
1264
+ continue;
1265
+ }
1266
+ newScripts[key] = resolved;
1267
+ result.scriptsAdded++;
1268
+ }
1269
+ merged.scripts = newScripts;
1270
+ }
1271
+ return merged;
1272
+ }
1273
+ function filterStylelintSettings(settings) {
1274
+ const filtered = Object.fromEntries(
1275
+ Object.entries(settings).filter(
1276
+ ([key]) => !STYLELINT_SETTINGS_PREFIXES.some((prefix) => key.startsWith(prefix))
1277
+ )
1278
+ );
1279
+ if (typeof filtered["editor.codeActionsOnSave"] === "object" && filtered["editor.codeActionsOnSave"] !== null) {
1280
+ const actions = { ...filtered["editor.codeActionsOnSave"] };
1281
+ delete actions["source.fixAll.stylelint"];
1282
+ filtered["editor.codeActionsOnSave"] = actions;
1283
+ }
1284
+ return filtered;
1285
+ }
1286
+ function resolveLocalDeps(deps) {
1287
+ const packages = [];
1288
+ for (const [name, version] of Object.entries(deps)) {
1289
+ if (version === "<latest>") {
1290
+ packages.push(name);
1291
+ } else {
1292
+ packages.push(`${name}@${version}`);
1293
+ }
1294
+ }
1295
+ return packages;
1296
+ }
1297
+
982
1298
  // src/commands/fmt.ts
983
1299
  function filterStylelintScripts(scripts) {
984
1300
  const filtered = {};
985
1301
  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, "");
1302
+ filtered[key] = value.replace(/\s*&&\s*stylelint\s+"[^"]*".*/g, "");
988
1303
  }
989
1304
  return filtered;
990
1305
  }
@@ -998,58 +1313,30 @@ function isNotEditorconfigDep(dep) {
998
1313
  }
999
1314
  function registerFmtCommand(program2) {
1000
1315
  const fmt = program2.command("fmt").description("Initialize formatting config with preset");
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(
1316
+ 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").option("--reset", "Reset local preset and re-materialize from built-in").action(
1002
1317
  async (presetName, options) => {
1003
1318
  const preset = resolvePreset(FMT_PRESETS, presetName);
1004
1319
  if (!preset) return;
1005
1320
  const cwd = process.cwd();
1006
- const pm = fileExists(path4.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1007
- const opts = {
1008
- cwd,
1009
- force: options.force ?? false,
1010
- dryRun: options.dryRun ?? false,
1011
- noStylelint: options.stylelint !== true,
1012
- noEditorconfig: options.editorconfig !== true,
1013
- lockfile: pm ? getLockfileName(pm) : void 0
1014
- };
1015
- const result = generateAllFmt(preset, opts);
1016
- const allFiles = [...result.created, ...result.overwritten];
1017
- if (allFiles.length === 0 && result.skipped.length === 0) {
1018
- logger.warn("No files to generate for this preset");
1019
- return;
1020
- }
1021
- logGenerationResult(result, opts.dryRun);
1022
- if (!pm) {
1023
- warnMissingPackageJson(preset, options.install !== false);
1024
- return;
1025
- }
1026
- const scripts = opts.noStylelint && preset.scripts ? filterStylelintScripts(preset.scripts) : preset.scripts;
1027
- if (scripts) {
1028
- await injectScripts(scripts, opts, pm);
1029
- }
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;
1033
- if (options.install === false) {
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");
1321
+ const pkgPath = path5.join(cwd, "package.json");
1322
+ if (fileExists(pkgPath)) {
1323
+ try {
1324
+ JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
1325
+ } catch {
1326
+ logger.error(
1327
+ "package.json exists but is not valid JSON. Fix it first, then re-run this command."
1328
+ );
1329
+ return;
1039
1330
  }
1040
- return;
1041
1331
  }
1042
- if (opts.dryRun) {
1043
- logger.log(`[dry-run] Would install: ${finalDeps.join(", ")}`);
1044
- return;
1332
+ if (options.reset) {
1333
+ resetLocalPreset("fmt", presetName);
1045
1334
  }
1046
- try {
1047
- logger.log(`Installing dependencies with ${pm}...`);
1048
- await installDevDeps(finalDeps, cwd, pm);
1049
- logger.success("Dependencies installed successfully");
1050
- } catch (error) {
1051
- const message = error instanceof Error ? error.message : String(error);
1052
- logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
1335
+ const useLocal = localPresetExists("fmt", presetName);
1336
+ if (useLocal) {
1337
+ await executeLocalPath(cwd, presetName, options);
1338
+ } else {
1339
+ await executeBuiltinPath(cwd, presetName, preset, options);
1053
1340
  }
1054
1341
  }
1055
1342
  );
@@ -1059,6 +1346,152 @@ function registerFmtCommand(program2) {
1059
1346
  }
1060
1347
  });
1061
1348
  }
1349
+ async function executeLocalPath(cwd, presetName, options) {
1350
+ logger.log("Using local custom preset");
1351
+ const opts = {
1352
+ cwd,
1353
+ force: options.force ?? false,
1354
+ dryRun: options.dryRun ?? false,
1355
+ noStylelint: options.stylelint !== true,
1356
+ noEditorconfig: options.editorconfig !== true
1357
+ };
1358
+ let result;
1359
+ try {
1360
+ result = applyLocalFmtPreset(cwd, presetName, opts);
1361
+ } catch (error) {
1362
+ if (error instanceof InvalidPackageJsonError) {
1363
+ logger.error(
1364
+ "package.json exists but is not valid JSON. Fix it first, then re-run this command."
1365
+ );
1366
+ return;
1367
+ }
1368
+ throw error;
1369
+ }
1370
+ const allFiles = [...result.created, ...result.overwritten];
1371
+ if (allFiles.length > 0 || result.skipped.length > 0) {
1372
+ logApplyResult(result);
1373
+ }
1374
+ if (result.scriptsAdded > 0 || result.scriptsSkipped > 0) {
1375
+ logger.log(
1376
+ `Added ${result.scriptsAdded} script${result.scriptsAdded > 1 ? "s" : ""} to package.json${result.scriptsSkipped > 0 ? ` (${result.scriptsSkipped} skipped)` : ""}`
1377
+ );
1378
+ }
1379
+ const pm = fileExists(path5.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1380
+ if (!pm) return;
1381
+ const templatePkgPath = path5.join(getLocalPresetDir("fmt", presetName), "package.json");
1382
+ const templatePkg = readJson(templatePkgPath);
1383
+ if (!templatePkg?.devDependencies) return;
1384
+ const depsToInstall = filterDeps(
1385
+ Object.keys(templatePkg.devDependencies),
1386
+ opts.noStylelint,
1387
+ opts.noEditorconfig
1388
+ );
1389
+ const projectPkgPath = path5.join(cwd, "package.json");
1390
+ const projectPkg = readJson(projectPkgPath);
1391
+ if (!projectPkg) return;
1392
+ const existingDeps = projectPkg.devDependencies ?? {};
1393
+ const missing = depsToInstall.filter((dep) => !existingDeps[dep]);
1394
+ if (missing.length === 0) return;
1395
+ if (opts.dryRun) {
1396
+ logger.log(`[dry-run] Would add to package.json: ${missing.join(", ")}`);
1397
+ return;
1398
+ }
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
+ }
1415
+ return;
1416
+ }
1417
+ try {
1418
+ logger.log(`Installing dependencies with ${pm}...`);
1419
+ const resolved = resolveLocalDeps(
1420
+ Object.fromEntries(
1421
+ Object.entries(templatePkg.devDependencies).filter(([k]) => missing.includes(k))
1422
+ )
1423
+ );
1424
+ await installDevDeps(resolved, cwd, pm);
1425
+ logger.success("Dependencies installed successfully");
1426
+ } catch (error) {
1427
+ const message = error instanceof Error ? error.message : String(error);
1428
+ logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
1429
+ }
1430
+ }
1431
+ async function executeBuiltinPath(cwd, presetName, preset, options) {
1432
+ const pm = fileExists(path5.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
1433
+ const opts = {
1434
+ cwd,
1435
+ force: options.force ?? false,
1436
+ dryRun: options.dryRun ?? false,
1437
+ noStylelint: options.stylelint !== true,
1438
+ noEditorconfig: options.editorconfig !== true,
1439
+ lockfile: pm ? getLockfileName(pm) : void 0
1440
+ };
1441
+ const result = generateAllFmt(preset, opts);
1442
+ const allFiles = [...result.created, ...result.overwritten];
1443
+ if (allFiles.length === 0 && result.skipped.length === 0) {
1444
+ logger.warn("No files to generate for this preset");
1445
+ return;
1446
+ }
1447
+ logGenerationResult(result, opts.dryRun);
1448
+ if (!opts.dryRun) {
1449
+ materializeFmtPreset(presetName, preset, opts);
1450
+ }
1451
+ if (!pm) {
1452
+ warnMissingPackageJson(preset, options.install !== false);
1453
+ return;
1454
+ }
1455
+ const scripts = opts.noStylelint && preset.scripts ? filterStylelintScripts(preset.scripts) : preset.scripts;
1456
+ if (scripts) {
1457
+ await injectScripts(scripts, opts, pm);
1458
+ }
1459
+ if (!preset.dependencies?.dev) return;
1460
+ const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep) : preset.dependencies.dev;
1461
+ const finalDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep) : devDeps;
1462
+ if (opts.dryRun) {
1463
+ logger.log(`[dry-run] Would add to package.json: ${finalDeps.join(", ")}`);
1464
+ return;
1465
+ }
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
+ }
1478
+ return;
1479
+ }
1480
+ try {
1481
+ logger.log(`Installing dependencies with ${pm}...`);
1482
+ await installDevDeps(finalDeps, cwd, pm);
1483
+ logger.success("Dependencies installed successfully");
1484
+ } catch (error) {
1485
+ const message = error instanceof Error ? error.message : String(error);
1486
+ logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
1487
+ }
1488
+ }
1489
+ function filterDeps(deps, noStylelint, noEditorconfig) {
1490
+ let filtered = deps;
1491
+ if (noStylelint) filtered = filtered.filter(isNotStylelintDep);
1492
+ if (noEditorconfig) filtered = filtered.filter(isNotEditorconfigDep);
1493
+ return filtered;
1494
+ }
1062
1495
  function logGenerationResult(result, dryRun) {
1063
1496
  const files = [...result.created, ...result.overwritten];
1064
1497
  if (dryRun) {
@@ -1086,6 +1519,23 @@ function logGenerationResult(result, dryRun) {
1086
1519
  );
1087
1520
  }
1088
1521
  }
1522
+ function logApplyResult(result) {
1523
+ if (result.created.length > 0) {
1524
+ logger.log(
1525
+ `Created ${summarizeFiles(result.created)} config ${result.created.length} file${result.created.length > 1 ? "s" : ""} from local preset`
1526
+ );
1527
+ }
1528
+ if (result.overwritten.length > 0) {
1529
+ logger.log(
1530
+ `Overwritten ${summarizeFiles(result.overwritten)} config ${result.overwritten.length} file${result.overwritten.length > 1 ? "s" : ""} from local preset`
1531
+ );
1532
+ }
1533
+ if (result.skipped.length > 0) {
1534
+ logger.log(
1535
+ `Skipped ${result.skipped.length} file${result.skipped.length > 1 ? "s" : ""} (already exists)`
1536
+ );
1537
+ }
1538
+ }
1089
1539
  function warnMissingPackageJson(preset, installEnabled) {
1090
1540
  const tasks = [];
1091
1541
  if (preset.scripts) tasks.push("script injection");
@@ -1106,7 +1556,7 @@ function summarizeFiles(filenames) {
1106
1556
  return [...categories].join(", ");
1107
1557
  }
1108
1558
  async function injectScripts(scripts, opts, pm) {
1109
- const pkgPath = path4.join(opts.cwd, "package.json");
1559
+ const pkgPath = path5.join(opts.cwd, "package.json");
1110
1560
  const pkg = readJson(pkgPath);
1111
1561
  if (!pkg) {
1112
1562
  logger.warn("package.json not found, skipping script injection");
@@ -1158,206 +1608,6 @@ var INIT_TOOLS = [
1158
1608
  }
1159
1609
  ];
1160
1610
 
1161
- // src/generators/init.ts
1162
- import fs2 from "fs";
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";
1238
- var CONFIG_DIR = ".lux";
1239
- var ENV_FILE = "env.txt";
1240
- function getEnvConfigPath() {
1241
- return path6.join(os.homedir(), CONFIG_DIR, ENV_FILE);
1242
- }
1243
- function getEnvConfig() {
1244
- let content;
1245
- try {
1246
- content = fs3.readFileSync(getEnvConfigPath(), "utf-8");
1247
- } catch {
1248
- return {};
1249
- }
1250
- const result = {};
1251
- for (const line of content.split("\n")) {
1252
- const trimmed = line.trim();
1253
- if (!trimmed || trimmed.startsWith("#")) continue;
1254
- const eqIndex = trimmed.indexOf("=");
1255
- if (eqIndex === -1) continue;
1256
- const key = trimmed.slice(0, eqIndex).trim();
1257
- const raw = trimmed.slice(eqIndex + 1).trim();
1258
- const value = raw.replace(/^["']|["']$/g, "");
1259
- if (key) result[key] = value;
1260
- }
1261
- return result;
1262
- }
1263
- function setEnvConfig(data) {
1264
- const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
1265
- writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
1266
- }
1267
- function clearEnvConfig() {
1268
- try {
1269
- fs3.unlinkSync(getEnvConfigPath());
1270
- } catch {
1271
- }
1272
- }
1273
-
1274
- // src/commands/show.ts
1275
- function handleShowEnv() {
1276
- const config = getEnvConfig();
1277
- const entries = Object.entries(config);
1278
- if (entries.length === 0) {
1279
- logger.log("No env config.");
1280
- return;
1281
- }
1282
- for (const [key, value] of entries) {
1283
- logger.log(`${key}="${value}"`);
1284
- }
1285
- }
1286
- function registerShowCommand(program2) {
1287
- const show = program2.command("show");
1288
- show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
1289
- }
1290
-
1291
- // src/utils/version.ts
1292
- import { existsSync, readFileSync } from "fs";
1293
- import { dirname, join } from "path";
1294
- import { fileURLToPath } from "url";
1295
- var PACKAGE_NAME = "@luxkit/cli";
1296
- var cachedVersion;
1297
- function getCurrentVersion() {
1298
- if (cachedVersion) return cachedVersion;
1299
- const __dirname = dirname(fileURLToPath(import.meta.url));
1300
- const candidates = [
1301
- join(__dirname, "..", "package.json"),
1302
- join(__dirname, "..", "..", "package.json")
1303
- ];
1304
- const pkgPath = candidates.find((p) => existsSync(p));
1305
- if (!pkgPath) {
1306
- throw new Error("Cannot locate package.json for version reading");
1307
- }
1308
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1309
- cachedVersion = pkg.version;
1310
- return cachedVersion;
1311
- }
1312
-
1313
- // src/commands/update.ts
1314
- var GLOBAL_UPDATE_CMDS = {
1315
- npm: ["npm", ["install", "-g", `${PACKAGE_NAME}@latest`]],
1316
- bun: ["bun", ["install", "-g", `${PACKAGE_NAME}@latest`]]
1317
- };
1318
- function detectGlobalPackageManager() {
1319
- return process.execPath.toLowerCase().includes("bun") ? "bun" : "npm";
1320
- }
1321
- async function fetchLatestVersion() {
1322
- const { stdout, exitCode } = await execFileNoThrow("npm", ["view", PACKAGE_NAME, "version"]);
1323
- if (exitCode !== 0 || !stdout) {
1324
- throw new Error(`Failed to fetch latest version from npm registry.`);
1325
- }
1326
- const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
1327
- return lines[lines.length - 1].trim();
1328
- }
1329
- async function performUpdate(pm) {
1330
- const [command, args] = GLOBAL_UPDATE_CMDS[pm];
1331
- const { exitCode, stderr } = await execFileNoThrow(command, args);
1332
- if (exitCode !== 0) {
1333
- const hint = /EACCES|permission/i.test(stderr) ? "\nTry running with elevated privileges (e.g. sudo)." : "";
1334
- throw new Error(`Update failed: ${stderr}${hint}`);
1335
- }
1336
- }
1337
- function registerUpdateCommand(program2) {
1338
- program2.command("update").description("Update @luxkit/cli to the latest version").option("--check", "Only check for updates without installing").action(async (options) => {
1339
- try {
1340
- const current = getCurrentVersion();
1341
- const latest = await fetchLatestVersion();
1342
- if (current === latest) {
1343
- logger.log(`Already up to date (v${current})`);
1344
- return;
1345
- }
1346
- if (options.check) {
1347
- logger.log(`Update available: v${current} \u2192 v${latest}`);
1348
- return;
1349
- }
1350
- const pm = detectGlobalPackageManager();
1351
- await performUpdate(pm);
1352
- logger.log(`Updated to v${latest}`);
1353
- } catch (err) {
1354
- const message = err instanceof Error ? err.message : String(err);
1355
- logger.error(message);
1356
- logger.warn(`You can also update manually: npm install -g ${PACKAGE_NAME}@latest`);
1357
- }
1358
- });
1359
- }
1360
-
1361
1611
  // src/presets/vscode/web-vue.ts
1362
1612
  var webVueVscode = {
1363
1613
  name: "web-vue",
@@ -2116,59 +2366,234 @@ var VSCODE_PRESETS = [
2116
2366
  goVscode
2117
2367
  ];
2118
2368
 
2119
- // src/core/merge-settings.ts
2120
- var USER_PRIORITY_KEYS = /* @__PURE__ */ new Set([
2121
- // Cursor/animation
2122
- "editor.cursorBlinking",
2123
- "editor.cursorSmoothCaretAnimation",
2124
- "editor.renderWhitespace",
2125
- "editor.guides.indentation",
2126
- "editor.largeFileOptimizations",
2127
- // Theme/appearance
2128
- "workbench.iconTheme",
2129
- "workbench.colorTheme",
2130
- // Suggestions
2131
- "editor.inlineSuggest.enabled",
2132
- "editor.suggestSelection",
2133
- "editor.acceptSuggestionOnEnter",
2134
- "editor.bracketPairColorization.enabled",
2135
- "editor.autoClosingBrackets",
2136
- "editor.autoClosingOvertype"
2137
- ]);
2138
- function mergeVscodeSettings(preset, existing) {
2139
- const result = { ...existing };
2140
- for (const [key, presetVal] of Object.entries(preset)) {
2141
- const existingVal = existing[key];
2142
- if (existingVal === void 0) {
2143
- result[key] = presetVal;
2144
- continue;
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);
2145
2386
  }
2146
- if (USER_PRIORITY_KEYS.has(key)) {
2147
- continue;
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;
2148
2415
  }
2149
- if (isPlainObject(presetVal) && isPlainObject(existingVal)) {
2150
- result[key] = mergeVscodeSettings(
2151
- presetVal,
2152
- existingVal
2153
- );
2154
- continue;
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;
2155
2427
  }
2156
- result[key] = presetVal;
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;
2157
2488
  }
2158
2489
  return result;
2159
2490
  }
2160
- function isPlainObject(val) {
2161
- return typeof val === "object" && val !== null && !Array.isArray(val);
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
+ });
2162
2587
  }
2163
2588
 
2164
2589
  // src/generators/vscode.ts
2165
- var STYLELINT_SETTINGS_PREFIXES = [
2590
+ var STYLELINT_SETTINGS_PREFIXES2 = [
2166
2591
  "stylelint.",
2167
2592
  "css.validate",
2168
2593
  "less.validate",
2169
2594
  "scss.validate"
2170
2595
  ];
2171
- var STYLELINT_EXTENSION = "stylelint.vscode-stylelint";
2596
+ var STYLELINT_EXTENSION2 = "stylelint.vscode-stylelint";
2172
2597
  function generateVscodeSettings(preset, opts) {
2173
2598
  const settingsPath = `${opts.cwd}/.vscode/settings.json`;
2174
2599
  if (opts.dryRun) {
@@ -2176,7 +2601,7 @@ function generateVscodeSettings(preset, opts) {
2176
2601
  return existingSettings2 ? "overwritten" : "created";
2177
2602
  }
2178
2603
  const rawSettings = preset.settings();
2179
- const presetSettings = opts.noStylelint ? filterStylelintSettings(rawSettings) : rawSettings;
2604
+ const presetSettings = opts.noStylelint ? filterStylelintSettings2(rawSettings) : rawSettings;
2180
2605
  const existingSettings = readJson(settingsPath);
2181
2606
  if (existingSettings) {
2182
2607
  const backupPath = `${settingsPath}.bak`;
@@ -2212,7 +2637,7 @@ function generateVscodeSettings(preset, opts) {
2212
2637
  }
2213
2638
  function generateVscodeExtensions(preset, opts) {
2214
2639
  if (opts.dryRun) return "created";
2215
- const extensions = opts.noStylelint ? preset.extensions().filter((ext) => ext !== STYLELINT_EXTENSION) : preset.extensions();
2640
+ const extensions = opts.noStylelint ? preset.extensions().filter((ext) => ext !== STYLELINT_EXTENSION2) : preset.extensions();
2216
2641
  try {
2217
2642
  writeJson(`${opts.cwd}/.vscode/extensions.json`, { recommendations: extensions });
2218
2643
  } catch (error) {
@@ -2231,10 +2656,10 @@ function generateAllVscode(preset, opts) {
2231
2656
  if (extAction === "created") result.created.push(".vscode/extensions.json");
2232
2657
  return result;
2233
2658
  }
2234
- function filterStylelintSettings(settings) {
2659
+ function filterStylelintSettings2(settings) {
2235
2660
  const filtered = Object.fromEntries(
2236
2661
  Object.entries(settings).filter(
2237
- ([key]) => !STYLELINT_SETTINGS_PREFIXES.some((prefix) => key.startsWith(prefix))
2662
+ ([key]) => !STYLELINT_SETTINGS_PREFIXES2.some((prefix) => key.startsWith(prefix))
2238
2663
  )
2239
2664
  );
2240
2665
  if (typeof filtered["editor.codeActionsOnSave"] === "object" && filtered["editor.codeActionsOnSave"] !== null) {
@@ -2248,29 +2673,20 @@ function filterStylelintSettings(settings) {
2248
2673
  // src/commands/vscode.ts
2249
2674
  function registerVscodeCommand(program2) {
2250
2675
  const vscode = program2.command("vscode").description("Initialize VSCode config with preset");
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(
2676
+ vscode.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--dry-run", "Preview without writing files").option("--stylelint", "Include Stylelint settings and extension").option("--reset", "Reset local preset and re-materialize from built-in").action(
2252
2677
  async (presetName, options) => {
2253
2678
  const preset = resolvePreset(VSCODE_PRESETS, presetName);
2254
2679
  if (!preset) return;
2255
2680
  const cwd = process.cwd();
2256
- const opts = {
2257
- cwd,
2258
- force: options.force ?? false,
2259
- dryRun: options.dryRun ?? false,
2260
- noStylelint: options.stylelint !== true,
2261
- noEditorconfig: false
2262
- };
2263
- const result = generateAllVscode(preset, opts);
2264
- const files = [...result.created, ...result.overwritten];
2265
- if (files.length === 0) {
2266
- logger.warn("No files generated");
2267
- return;
2681
+ if (options.reset) {
2682
+ resetLocalPreset("vscode", presetName);
2268
2683
  }
2269
- if (opts.dryRun) {
2270
- logger.log(`[dry-run] Would create ${files.join(", ")}`);
2271
- return;
2684
+ const useLocal = localPresetExists("vscode", presetName);
2685
+ if (useLocal) {
2686
+ executeVscodeLocalPath(cwd, presetName, options);
2687
+ } else {
2688
+ executeVscodeBuiltinPath(cwd, presetName, preset, options);
2272
2689
  }
2273
- logger.log(`Created ${files.join(", ")}`);
2274
2690
  }
2275
2691
  );
2276
2692
  vscode.command("list").description("List available vscode presets").action(() => {
@@ -2279,6 +2695,48 @@ function registerVscodeCommand(program2) {
2279
2695
  }
2280
2696
  });
2281
2697
  }
2698
+ function executeVscodeLocalPath(cwd, presetName, options) {
2699
+ logger.log("Using local custom preset");
2700
+ const opts = {
2701
+ cwd,
2702
+ force: options.force ?? false,
2703
+ dryRun: options.dryRun ?? false,
2704
+ noStylelint: options.stylelint !== true,
2705
+ noEditorconfig: false
2706
+ };
2707
+ const result = applyLocalVscodePreset(cwd, presetName, opts);
2708
+ const files = [...result.created, ...result.overwritten];
2709
+ if (files.length === 0) {
2710
+ logger.warn("No files generated");
2711
+ return;
2712
+ }
2713
+ if (opts.dryRun) {
2714
+ logger.log(`[dry-run] Would create ${files.join(", ")} from local preset`);
2715
+ return;
2716
+ }
2717
+ logger.log(`Created ${files.join(", ")} from local preset`);
2718
+ }
2719
+ function executeVscodeBuiltinPath(cwd, presetName, preset, options) {
2720
+ const opts = {
2721
+ cwd,
2722
+ force: options.force ?? false,
2723
+ dryRun: options.dryRun ?? false,
2724
+ noStylelint: options.stylelint !== true,
2725
+ noEditorconfig: false
2726
+ };
2727
+ const result = generateAllVscode(preset, opts);
2728
+ const files = [...result.created, ...result.overwritten];
2729
+ if (files.length === 0) {
2730
+ logger.warn("No files generated");
2731
+ return;
2732
+ }
2733
+ if (opts.dryRun) {
2734
+ logger.log(`[dry-run] Would create ${files.join(", ")}`);
2735
+ return;
2736
+ }
2737
+ logger.log(`Created ${files.join(", ")}`);
2738
+ materializeVscodePreset(cwd, presetName);
2739
+ }
2282
2740
 
2283
2741
  // src/commands/vpn.ts
2284
2742
  import { spawnSync } from "child_process";