@luxkit/cli 1.1.1 → 1.1.3

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
@@ -6,19 +6,20 @@ import { program } from "commander";
6
6
  // src/commands/fmt.ts
7
7
  import fs3 from "fs";
8
8
  import path5 from "path";
9
+ import chalk2 from "chalk";
9
10
 
10
11
  // src/presets/fmt/electron-vue.ts
11
12
  var electronVueFmt = {
12
13
  name: "electron-vue",
13
14
  description: "Vue 3 + Electron desktop app",
14
15
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
15
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
16
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
16
17
  import pluginVue from 'eslint-plugin-vue'
17
18
 
18
19
  export default [
19
20
  ...pluginVue.configs['flat/recommended'],
20
21
  ...withVue(),
21
- ...withPrettier(),
22
+ prettierConfig,
22
23
  {
23
24
  rules: {
24
25
  'vue/multi-word-component-names': 'off',
@@ -79,7 +80,8 @@ out/
79
80
  version: "0.2",
80
81
  language: "en,en-US",
81
82
  allowCompoundWords: true,
82
- words: ["vite", "pinia", "vueuse", "unplugin", "electron", "electron-builder"]
83
+ words: ["vite", "pinia", "vueuse", "unplugin", "electron", "electron-builder"],
84
+ ignorePaths: ["**/*.svg", "**/*.png"]
83
85
  },
84
86
  null,
85
87
  2
@@ -115,18 +117,9 @@ trim_trailing_whitespace = false
115
117
  ]
116
118
  },
117
119
  scripts: {
118
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
119
- "lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
120
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
121
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
122
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
123
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
124
- cspell: 'cspell --gitignore "src/**/*"',
125
- "type:check": "vue-tsc --noEmit",
126
- "code:check": "<pm> lint && <pm> format:check",
127
- "code:fix": "<pm> lint:fix && <pm> format",
128
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
129
- "code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
120
+ 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/',
121
+ "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/',
122
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
130
123
  }
131
124
  };
132
125
 
@@ -163,7 +156,8 @@ coverage/
163
156
  version: "0.2",
164
157
  language: "en,en-US",
165
158
  allowCompoundWords: true,
166
- words: ["nestjs", "typeorm", "dtos"]
159
+ words: ["nestjs", "typeorm", "dtos"],
160
+ ignorePaths: ["**/*.svg", "**/*.png"]
167
161
  },
168
162
  null,
169
163
  2
@@ -184,12 +178,10 @@ trim_trailing_whitespace = false
184
178
  dependencies: {
185
179
  dev: ["prettier", "cspell"]
186
180
  },
187
- // NestJS: only append new scripts, don't conflict with existing ones
188
181
  scripts: {
189
- cspell: 'cspell --gitignore "src/**/*"',
190
- "type:check": "tsc --noEmit",
191
- "code:check": "<pm> lint && <pm> format:check",
192
- "code:check:all": "<pm> lint && <pm> format:check && <pm> cspell"
182
+ 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',
183
+ "lint:fix": 'eslint "{src,apps,libs,test}/**/*.ts" --cache --cache-location node_modules/.cache/eslint --fix',
184
+ format: 'prettier --write "src/**/*.{ts,js,json}"'
193
185
  }
194
186
  };
195
187
 
@@ -268,7 +260,8 @@ coverage/
268
260
  version: "0.2",
269
261
  language: "en,en-US",
270
262
  allowCompoundWords: true,
271
- words: []
263
+ words: [],
264
+ ignorePaths: ["**/*.svg", "**/*.png"]
272
265
  },
273
266
  null,
274
267
  2
@@ -298,16 +291,9 @@ trim_trailing_whitespace = false
298
291
  ]
299
292
  },
300
293
  scripts: {
301
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
302
- "lint:fix": 'eslint "src/**/*.{js,ts}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
303
- format: 'prettier --write "src/**/*.{ts,js,json}"',
304
- "format:check": 'prettier --check "src/**/*.{ts,js,json}"',
305
- cspell: 'cspell --gitignore "src/**/*"',
306
- "type:check": "tsc --noEmit",
307
- "code:check": "<pm> lint && <pm> format:check",
308
- "code:fix": "<pm> lint:fix && <pm> format",
309
- "code:check:all": "<pm> lint && <pm> format:check && <pm> cspell",
310
- "code:fix:all": "<pm> lint:fix && <pm> format"
294
+ lint: 'eslint . --cache --cache-location node_modules/.cache/eslint && cspell --cache --cache-location node_modules/.cache/cspell --gitignore "src/**/*" && tsc --noEmit',
295
+ "lint:fix": "eslint . --cache --cache-location node_modules/.cache/eslint --fix",
296
+ format: 'prettier --write "src/**/*.{ts,js,json}"'
311
297
  }
312
298
  };
313
299
 
@@ -316,13 +302,13 @@ var uniappFmt = {
316
302
  name: "uniapp",
317
303
  description: "Vue 3 + UniApp WeChat mini program",
318
304
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
319
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
305
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
320
306
  import pluginVue from 'eslint-plugin-vue'
321
307
 
322
308
  export default [
323
309
  ...pluginVue.configs['flat/recommended'],
324
310
  ...withVue(),
325
- ...withPrettier(),
311
+ prettierConfig,
326
312
  {
327
313
  rules: {
328
314
  'vue/multi-word-component-names': 'off',
@@ -381,7 +367,8 @@ unpackage/
381
367
  version: "0.2",
382
368
  language: "en,en-US",
383
369
  allowCompoundWords: true,
384
- words: ["vite", "pinia", "vueuse", "unplugin", "uniapp"]
370
+ words: ["vite", "pinia", "vueuse", "unplugin", "uniapp"],
371
+ ignorePaths: ["**/*.svg", "**/*.png"]
385
372
  },
386
373
  null,
387
374
  2
@@ -417,18 +404,9 @@ trim_trailing_whitespace = false
417
404
  ]
418
405
  },
419
406
  scripts: {
420
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
421
- "lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
422
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
423
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
424
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
425
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
426
- cspell: 'cspell --gitignore "src/**/*"',
427
- "type:check": "vue-tsc --noEmit",
428
- "code:check": "<pm> lint && <pm> format:check",
429
- "code:fix": "<pm> lint:fix && <pm> format",
430
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
431
- "code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
407
+ 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/',
408
+ "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/',
409
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
432
410
  }
433
411
  };
434
412
 
@@ -507,7 +485,8 @@ dist/
507
485
  version: "0.2",
508
486
  language: "en,en-US",
509
487
  allowCompoundWords: true,
510
- words: ["vite", "react", "zustand", "tanstack"]
488
+ words: ["vite", "react", "zustand", "tanstack"],
489
+ ignorePaths: ["**/*.svg", "**/*.png"]
511
490
  },
512
491
  null,
513
492
  2
@@ -545,18 +524,9 @@ trim_trailing_whitespace = false
545
524
  ]
546
525
  },
547
526
  scripts: {
548
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
549
- "lint:fix": 'eslint "src/**/*.{js,ts,jsx,tsx}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
550
- format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"',
551
- "format:check": 'prettier --check "src/**/*.{ts,js,json,jsx,tsx,css,scss}"',
552
- stylelint: 'stylelint "src/**/*.{css,scss}"',
553
- "stylelint:fix": 'stylelint "src/**/*.{css,scss}" --fix',
554
- cspell: 'cspell --gitignore "src/**/*"',
555
- "type:check": "tsc --noEmit",
556
- "code:check": "<pm> lint && <pm> format:check",
557
- "code:fix": "<pm> lint:fix && <pm> format",
558
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
559
- "code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
527
+ 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/',
528
+ "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/',
529
+ format: 'prettier --write "src/**/*.{ts,js,json,jsx,tsx,css,scss}"'
560
530
  }
561
531
  };
562
532
 
@@ -565,13 +535,13 @@ var webVueFmt = {
565
535
  name: "web-vue",
566
536
  description: "Vue 3 Web frontend (Vite + Vue + TypeScript)",
567
537
  eslint: () => `import withVue from '@vue/eslint-config-typescript'
568
- import withPrettier from '@vue/eslint-config-prettier/skip-formatting'
538
+ import prettierConfig from '@vue/eslint-config-prettier/skip-formatting'
569
539
  import pluginVue from 'eslint-plugin-vue'
570
540
 
571
541
  export default [
572
542
  ...pluginVue.configs['flat/recommended'],
573
543
  ...withVue(),
574
- ...withPrettier(),
544
+ prettierConfig,
575
545
  {
576
546
  rules: {
577
547
  'vue/multi-word-component-names': 'off',
@@ -628,7 +598,8 @@ dist/
628
598
  version: "0.2",
629
599
  language: "en,en-US",
630
600
  allowCompoundWords: true,
631
- words: ["vite", "pinia", "vueuse", "unplugin"]
601
+ words: ["vite", "pinia", "vueuse", "unplugin"],
602
+ ignorePaths: ["**/*.svg", "**/*.png"]
632
603
  },
633
604
  null,
634
605
  2
@@ -664,18 +635,9 @@ trim_trailing_whitespace = false
664
635
  ]
665
636
  },
666
637
  scripts: {
667
- lint: "eslint . --cache --cache-location node_modules/.cache/.eslintcache",
668
- "lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix --cache --cache-location node_modules/.cache/.eslintcache',
669
- format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
670
- "format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
671
- stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
672
- "stylelint:fix": 'stylelint "src/**/*.{css,scss,vue}" --fix',
673
- cspell: 'cspell --gitignore "src/**/*"',
674
- "type:check": "vue-tsc --noEmit",
675
- "code:check": "<pm> lint && <pm> format:check",
676
- "code:fix": "<pm> lint:fix && <pm> format",
677
- "code:check:all": "<pm> lint && <pm> format:check && <pm> stylelint && <pm> cspell",
678
- "code:fix:all": "<pm> lint:fix && <pm> format && <pm> stylelint:fix"
638
+ 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/',
639
+ "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/',
640
+ format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"'
679
641
  }
680
642
  };
681
643
 
@@ -707,7 +669,6 @@ var logger = {
707
669
  };
708
670
 
709
671
  // src/utils/errors.ts
710
- import chalk2 from "chalk";
711
672
  var CliError = class extends Error {
712
673
  code;
713
674
  suggestion;
@@ -753,14 +714,6 @@ function levenshtein(a, b) {
753
714
  }
754
715
  function resolvePreset(presets, name) {
755
716
  const found = presets.find((p) => p.name === name);
756
- if (!found) {
757
- const err = new PresetNotFoundError(
758
- name,
759
- presets.map((p) => p.name)
760
- );
761
- console.error(chalk2.red(err.message));
762
- process.exit(1);
763
- }
764
717
  return found;
765
718
  }
766
719
 
@@ -1070,6 +1023,28 @@ function getLocalPresetDir(type, presetName) {
1070
1023
  function isValidPresetName(name) {
1071
1024
  return name.length > 0 && !name.includes("/") && !name.includes("\\") && !name.includes("..");
1072
1025
  }
1026
+ function listCustomPresets() {
1027
+ const fmtDir = path4.join(getLuxDir(), "preset", "fmt");
1028
+ if (!fs2.existsSync(fmtDir)) return [];
1029
+ const entries = fs2.readdirSync(fmtDir, { withFileTypes: true });
1030
+ const result = [];
1031
+ for (const entry of entries) {
1032
+ if (!entry.isDirectory()) continue;
1033
+ if (!isValidPresetName(entry.name)) continue;
1034
+ const pkgPath = path4.join(fmtDir, entry.name, "package.json");
1035
+ if (fs2.existsSync(pkgPath)) {
1036
+ result.push(entry.name);
1037
+ }
1038
+ }
1039
+ return result;
1040
+ }
1041
+ function isValidCustomPreset(name) {
1042
+ if (!isValidPresetName(name)) return false;
1043
+ const presetDir = path4.join(getLuxDir(), "preset", "fmt", name);
1044
+ if (!fs2.existsSync(presetDir)) return false;
1045
+ const pkgPath = path4.join(presetDir, "package.json");
1046
+ return fs2.existsSync(pkgPath);
1047
+ }
1073
1048
  function localPresetExists(type, presetName) {
1074
1049
  const dir = getLocalPresetDir(type, presetName);
1075
1050
  return fs2.existsSync(dir);
@@ -1113,6 +1088,15 @@ function materializeVscodePreset(cwd, presetName) {
1113
1088
  }
1114
1089
  logger.log(`Local preset created at ${presetDir}`);
1115
1090
  }
1091
+ function materializeVscodePresetFromBuiltin(presetName, preset) {
1092
+ const presetDir = getLocalPresetDir("vscode", presetName);
1093
+ ensureDir(presetDir);
1094
+ const settings = preset.settings();
1095
+ writeJson(path4.join(presetDir, "settings.json"), settings);
1096
+ const extensions = preset.extensions();
1097
+ writeJson(path4.join(presetDir, "extensions.json"), { recommendations: extensions });
1098
+ logger.log(`Local preset created at ${presetDir}`);
1099
+ }
1116
1100
  var InvalidPackageJsonError = class extends Error {
1117
1101
  constructor(filePath) {
1118
1102
  super(`package.json exists but is not valid JSON: ${filePath}`);
@@ -1220,7 +1204,9 @@ function applyLocalVscodePreset(cwd, presetName, opts) {
1220
1204
  if (extensionsData) {
1221
1205
  let presetRecommendations = extensionsData.recommendations ?? [];
1222
1206
  if (opts.noStylelint) {
1223
- presetRecommendations = presetRecommendations.filter((ext) => ext !== STYLELINT_EXTENSION);
1207
+ presetRecommendations = presetRecommendations.filter(
1208
+ (ext) => ext !== STYLELINT_EXTENSION
1209
+ );
1224
1210
  }
1225
1211
  if (opts.dryRun) {
1226
1212
  result.created.push(".vscode/extensions.json");
@@ -1272,8 +1258,12 @@ function mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result) {
1272
1258
  if (templatePkg.scripts) {
1273
1259
  const existingScripts = merged.scripts ?? {};
1274
1260
  const newScripts = { ...existingScripts };
1275
- for (const [key, value] of Object.entries(templatePkg.scripts)) {
1276
- if (opts.noStylelint && key.startsWith("stylelint")) continue;
1261
+ const filteredScripts = filterScripts(
1262
+ templatePkg.scripts,
1263
+ opts.noStylelint,
1264
+ opts.noEditorconfig
1265
+ );
1266
+ for (const [key, value] of Object.entries(filteredScripts)) {
1277
1267
  const resolved = value.replace(/<pm>/g, prefix);
1278
1268
  if (existingScripts[key] !== void 0 && !opts.force) {
1279
1269
  result.scriptsSkipped++;
@@ -1309,6 +1299,42 @@ function filterStylelintSettings(settings) {
1309
1299
  }
1310
1300
  return filtered;
1311
1301
  }
1302
+ function filterScripts(scripts, noStylelint, noEditorconfig) {
1303
+ const filtered = {};
1304
+ for (const [key, value] of Object.entries(scripts)) {
1305
+ if (noStylelint && key.includes("stylelint")) continue;
1306
+ if (noEditorconfig && key.includes("editorconfig")) continue;
1307
+ let resolved = value;
1308
+ if (noStylelint) {
1309
+ resolved = resolved.replace(/\s*&&\s*stylelint\s+"[^"]*".*/g, "");
1310
+ }
1311
+ filtered[key] = resolved;
1312
+ }
1313
+ return filtered;
1314
+ }
1315
+ function detectPresetCapabilities(presetName) {
1316
+ const presetDir = path4.join(getLuxDir(), "preset", "fmt", presetName);
1317
+ const entries = fs2.readdirSync(presetDir);
1318
+ const hasStylelintFile = entries.some((f) => STYLELINT_FILES.has(f));
1319
+ const pkg = readJson(
1320
+ path4.join(presetDir, "package.json")
1321
+ );
1322
+ const hasStylelintDep = pkg?.devDependencies ? Object.keys(pkg.devDependencies).some((d) => isNotStylelintDep(d) === false) : false;
1323
+ const hasEditorconfigFile = entries.includes(EDITORCONFIG_FILE);
1324
+ const hasEditorconfigDep = pkg?.devDependencies ? Object.keys(pkg.devDependencies).some((d) => !isNotEditorconfigDep(d)) : false;
1325
+ return {
1326
+ hasStylelint: hasStylelintFile || hasStylelintDep,
1327
+ hasEditorconfig: hasEditorconfigFile || hasEditorconfigDep
1328
+ };
1329
+ }
1330
+ function isNotStylelintDep(dep) {
1331
+ if (dep.includes("stylelint")) return false;
1332
+ if (dep === "postcss-html" || dep === "postcss-scss") return false;
1333
+ return true;
1334
+ }
1335
+ function isNotEditorconfigDep(dep) {
1336
+ return !dep.includes("editorconfig");
1337
+ }
1312
1338
  function resolveLocalDeps(deps) {
1313
1339
  const packages = [];
1314
1340
  for (const [name, version] of Object.entries(deps)) {
@@ -1322,28 +1348,24 @@ function resolveLocalDeps(deps) {
1322
1348
  }
1323
1349
 
1324
1350
  // src/commands/fmt.ts
1325
- function filterStylelintScripts(scripts) {
1326
- const filtered = {};
1327
- for (const [key, value] of Object.entries(scripts)) {
1328
- if (key.startsWith("stylelint")) continue;
1329
- filtered[key] = value.replace(/\s*&&\s*<pm>\s+stylelint\S*/g, "");
1330
- }
1331
- return filtered;
1332
- }
1333
- function isNotStylelintDep(dep) {
1351
+ function isNotStylelintDep2(dep) {
1334
1352
  if (dep.includes("stylelint")) return false;
1335
1353
  if (dep === "postcss-html" || dep === "postcss-scss") return false;
1336
1354
  return true;
1337
1355
  }
1338
- function isNotEditorconfigDep(dep) {
1356
+ function isNotEditorconfigDep2(dep) {
1339
1357
  return !dep.includes("editorconfig");
1340
1358
  }
1341
1359
  function registerFmtCommand(program2) {
1342
1360
  const fmt = program2.command("fmt").description("Initialize formatting config with preset");
1343
1361
  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(
1344
1362
  async (presetName, options) => {
1345
- const preset = resolvePreset(FMT_PRESETS, presetName);
1346
- if (!preset) return;
1363
+ const builtinPreset = FMT_PRESETS.find((p) => p.name === presetName);
1364
+ const isBuiltin = builtinPreset !== void 0;
1365
+ if (options.reset && !isBuiltin) {
1366
+ logger.warn(`"${presetName}" is a custom preset, --reset has no builtin to restore`);
1367
+ return;
1368
+ }
1347
1369
  const cwd = process.cwd();
1348
1370
  const pkgPath = path5.join(cwd, "package.json");
1349
1371
  if (fileExists(pkgPath)) {
@@ -1356,25 +1378,52 @@ function registerFmtCommand(program2) {
1356
1378
  return;
1357
1379
  }
1358
1380
  }
1359
- if (options.reset) {
1360
- resetLocalPreset("fmt", presetName);
1361
- }
1362
- const useLocal = localPresetExists("fmt", presetName);
1363
- if (useLocal) {
1381
+ if (isBuiltin) {
1382
+ if (options.reset) {
1383
+ resetLocalPreset("fmt", presetName);
1384
+ }
1385
+ const useLocal = localPresetExists("fmt", presetName);
1386
+ if (useLocal) {
1387
+ await executeLocalPath(cwd, presetName, options);
1388
+ } else {
1389
+ await executeBuiltinPath(cwd, presetName, builtinPreset, options);
1390
+ }
1391
+ } else if (isValidCustomPreset(presetName)) {
1364
1392
  await executeLocalPath(cwd, presetName, options);
1365
1393
  } else {
1366
- await executeBuiltinPath(cwd, presetName, preset, options);
1394
+ const builtinNames = new Set(FMT_PRESETS.map((p) => p.name));
1395
+ const customNames = listCustomPresets().filter((n) => !builtinNames.has(n));
1396
+ const allNames = [...builtinNames, ...customNames];
1397
+ const err = new PresetNotFoundError(presetName, allNames);
1398
+ logger.error(err.message);
1399
+ process.exitCode = 1;
1367
1400
  }
1368
1401
  }
1369
1402
  );
1370
1403
  fmt.command("list").description("List available fmt presets").action(() => {
1404
+ const builtinNames = new Set(FMT_PRESETS.map((p) => p.name));
1371
1405
  for (const p of FMT_PRESETS) {
1372
1406
  console.log(`${p.name.padEnd(12)} ${p.description}`);
1373
1407
  }
1408
+ const customs = listCustomPresets().filter((name) => !builtinNames.has(name));
1409
+ for (const name of customs) {
1410
+ console.log(`${name.padEnd(12)} ${chalk2.yellow("(custom)")}`);
1411
+ }
1374
1412
  });
1375
1413
  }
1376
1414
  async function executeLocalPath(cwd, presetName, options) {
1377
1415
  logger.log("Using local custom preset");
1416
+ const caps = detectPresetCapabilities(presetName);
1417
+ if (options.stylelint && !caps.hasStylelint) {
1418
+ logger.warn(
1419
+ "--stylelint has no effect: this custom preset has no stylelint config or dependencies"
1420
+ );
1421
+ }
1422
+ if (options.editorconfig && !caps.hasEditorconfig) {
1423
+ logger.warn(
1424
+ "--editorconfig has no effect: this custom preset has no editorconfig config or dependencies"
1425
+ );
1426
+ }
1378
1427
  const opts = {
1379
1428
  cwd,
1380
1429
  force: options.force ?? false,
@@ -1419,18 +1468,26 @@ async function executeLocalPath(cwd, presetName, options) {
1419
1468
  const existingDeps = projectPkg.devDependencies ?? {};
1420
1469
  const missing = depsToInstall.filter((dep) => !existingDeps[dep]);
1421
1470
  if (missing.length === 0) return;
1422
- if (options.install === false) {
1423
- const resolved = resolveLocalDeps(templatePkg.devDependencies);
1424
- const added = await addDepsToManifest(resolved, cwd);
1425
- if (added.length > 0) {
1426
- logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
1427
- } else {
1428
- logger.log("All dependencies already in package.json");
1429
- }
1471
+ if (opts.dryRun) {
1472
+ logger.log(`[dry-run] Would add to package.json: ${missing.join(", ")}`);
1430
1473
  return;
1431
1474
  }
1432
- if (opts.dryRun) {
1433
- logger.log(`[dry-run] Would install: ${missing.join(", ")}`);
1475
+ if (options.install === false) {
1476
+ try {
1477
+ const filteredTemplateDeps = Object.fromEntries(
1478
+ Object.entries(templatePkg.devDependencies).filter(([k]) => missing.includes(k))
1479
+ );
1480
+ const resolved = resolveLocalDeps(filteredTemplateDeps);
1481
+ const added = await addDepsToManifest(resolved, cwd);
1482
+ if (added.length > 0) {
1483
+ logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
1484
+ } else {
1485
+ logger.log("All dependencies already in package.json");
1486
+ }
1487
+ } catch (error) {
1488
+ const message = error instanceof Error ? error.message : String(error);
1489
+ logger.warn(`Failed to fetch versions: ${message}. You can add dependencies manually.`);
1490
+ }
1434
1491
  return;
1435
1492
  }
1436
1493
  try {
@@ -1471,24 +1528,29 @@ async function executeBuiltinPath(cwd, presetName, preset, options) {
1471
1528
  warnMissingPackageJson(preset, options.install !== false);
1472
1529
  return;
1473
1530
  }
1474
- const scripts = opts.noStylelint && preset.scripts ? filterStylelintScripts(preset.scripts) : preset.scripts;
1531
+ const scripts = preset.scripts ? filterScripts(preset.scripts, opts.noStylelint, opts.noEditorconfig) : void 0;
1475
1532
  if (scripts) {
1476
1533
  await injectScripts(scripts, opts, pm);
1477
1534
  }
1478
1535
  if (!preset.dependencies?.dev) return;
1479
- const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep) : preset.dependencies.dev;
1480
- const finalDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep) : devDeps;
1481
- if (options.install === false) {
1482
- const added = await addDepsToManifest(finalDeps, cwd);
1483
- if (added.length > 0) {
1484
- logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
1485
- } else {
1486
- logger.log("All dependencies already in package.json");
1487
- }
1536
+ const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep2) : preset.dependencies.dev;
1537
+ const finalDeps = opts.noEditorconfig ? devDeps.filter(isNotEditorconfigDep2) : devDeps;
1538
+ if (opts.dryRun) {
1539
+ logger.log(`[dry-run] Would add to package.json: ${finalDeps.join(", ")}`);
1488
1540
  return;
1489
1541
  }
1490
- if (opts.dryRun) {
1491
- logger.log(`[dry-run] Would install: ${finalDeps.join(", ")}`);
1542
+ if (options.install === false) {
1543
+ try {
1544
+ const added = await addDepsToManifest(finalDeps, cwd);
1545
+ if (added.length > 0) {
1546
+ logger.success(`Added to package.json (skipped install): ${added.join(", ")}`);
1547
+ } else {
1548
+ logger.log("All dependencies already in package.json");
1549
+ }
1550
+ } catch (error) {
1551
+ const message = error instanceof Error ? error.message : String(error);
1552
+ logger.warn(`Failed to fetch versions: ${message}. You can add dependencies manually.`);
1553
+ }
1492
1554
  return;
1493
1555
  }
1494
1556
  try {
@@ -1502,8 +1564,8 @@ async function executeBuiltinPath(cwd, presetName, preset, options) {
1502
1564
  }
1503
1565
  function filterDeps(deps, noStylelint, noEditorconfig) {
1504
1566
  let filtered = deps;
1505
- if (noStylelint) filtered = filtered.filter(isNotStylelintDep);
1506
- if (noEditorconfig) filtered = filtered.filter(isNotEditorconfigDep);
1567
+ if (noStylelint) filtered = filtered.filter(isNotStylelintDep2);
1568
+ if (noEditorconfig) filtered = filtered.filter(isNotEditorconfigDep2);
1507
1569
  return filtered;
1508
1570
  }
1509
1571
  function logGenerationResult(result, dryRun) {
@@ -1622,206 +1684,6 @@ var INIT_TOOLS = [
1622
1684
  }
1623
1685
  ];
1624
1686
 
1625
- // src/generators/init.ts
1626
- import fs4 from "fs";
1627
- import path6 from "path";
1628
- function resolveSkillsDir() {
1629
- const entryDir = path6.dirname(process.argv[1] ?? "");
1630
- return path6.resolve(entryDir, "skills");
1631
- }
1632
- function listFilesRecursive(dir, base) {
1633
- const entries = fs4.readdirSync(dir, { withFileTypes: true });
1634
- const files = [];
1635
- for (const entry of entries) {
1636
- const childBase = `${base}/${entry.name}`;
1637
- const fullPath = path6.join(dir, entry.name);
1638
- if (entry.isDirectory()) {
1639
- files.push(...listFilesRecursive(fullPath, childBase));
1640
- } else {
1641
- files.push(childBase);
1642
- }
1643
- }
1644
- return files;
1645
- }
1646
- function generateInitSkills(targetBaseDir, cwd) {
1647
- const skillsDir = resolveSkillsDir();
1648
- if (!fs4.existsSync(skillsDir)) {
1649
- logger.error(`Bundled skills directory not found: ${skillsDir}`);
1650
- logger.error('Please run "lux build" or reinstall lux.');
1651
- return { copiedFiles: [], targetDir: targetBaseDir };
1652
- }
1653
- const targetPath = path6.resolve(cwd, targetBaseDir);
1654
- try {
1655
- fs4.cpSync(skillsDir, targetPath, { recursive: true, force: true });
1656
- } catch (error) {
1657
- const message = error instanceof Error ? error.message : String(error);
1658
- logger.error(`Failed to copy skills to ${targetPath}: ${message}`);
1659
- return { copiedFiles: [], targetDir: targetBaseDir };
1660
- }
1661
- const copiedFiles = fs4.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
1662
- return { copiedFiles, targetDir: targetBaseDir };
1663
- }
1664
-
1665
- // src/commands/init.ts
1666
- function registerInitCommand(program2) {
1667
- program2.command("init").description("Initialize AI coding tool skills in current project").action(async () => {
1668
- const toolOptions = INIT_TOOLS.map((tool2) => ({
1669
- value: tool2.name,
1670
- label: tool2.label
1671
- }));
1672
- const selected = await select({
1673
- message: "Which AI coding tool do you use?",
1674
- options: toolOptions
1675
- });
1676
- if (isCancel(selected)) {
1677
- cancel("Operation cancelled.");
1678
- return;
1679
- }
1680
- const tool = INIT_TOOLS.find((t) => t.name === selected);
1681
- if (!tool) {
1682
- logger.error(`Unknown tool: ${String(selected)}`);
1683
- return;
1684
- }
1685
- const cwd = process.cwd();
1686
- const result = generateInitSkills(tool.targetDir, cwd);
1687
- if (result.copiedFiles.length === 0) {
1688
- logger.warn("No skill files were copied.");
1689
- return;
1690
- }
1691
- for (const file of result.copiedFiles) {
1692
- logger.log(` ${file}`);
1693
- }
1694
- outro(`Skills installed to ${tool.targetDir}/`);
1695
- });
1696
- }
1697
-
1698
- // src/utils/config.ts
1699
- import fs5 from "fs";
1700
- import os2 from "os";
1701
- import path7 from "path";
1702
- var CONFIG_DIR = ".lux";
1703
- var ENV_FILE = "env.txt";
1704
- function getEnvConfigPath() {
1705
- return path7.join(os2.homedir(), CONFIG_DIR, ENV_FILE);
1706
- }
1707
- function getEnvConfig() {
1708
- let content;
1709
- try {
1710
- content = fs5.readFileSync(getEnvConfigPath(), "utf-8");
1711
- } catch {
1712
- return {};
1713
- }
1714
- const result = {};
1715
- for (const line of content.split("\n")) {
1716
- const trimmed = line.trim();
1717
- if (!trimmed || trimmed.startsWith("#")) continue;
1718
- const eqIndex = trimmed.indexOf("=");
1719
- if (eqIndex === -1) continue;
1720
- const key = trimmed.slice(0, eqIndex).trim();
1721
- const raw = trimmed.slice(eqIndex + 1).trim();
1722
- const value = raw.replace(/^["']|["']$/g, "");
1723
- if (key) result[key] = value;
1724
- }
1725
- return result;
1726
- }
1727
- function setEnvConfig(data) {
1728
- const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
1729
- writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
1730
- }
1731
- function clearEnvConfig() {
1732
- try {
1733
- fs5.unlinkSync(getEnvConfigPath());
1734
- } catch {
1735
- }
1736
- }
1737
-
1738
- // src/commands/show.ts
1739
- function handleShowEnv() {
1740
- const config = getEnvConfig();
1741
- const entries = Object.entries(config);
1742
- if (entries.length === 0) {
1743
- logger.log("No env config.");
1744
- return;
1745
- }
1746
- for (const [key, value] of entries) {
1747
- logger.log(`${key}="${value}"`);
1748
- }
1749
- }
1750
- function registerShowCommand(program2) {
1751
- const show = program2.command("show");
1752
- show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
1753
- }
1754
-
1755
- // src/utils/version.ts
1756
- import { existsSync, readFileSync } from "fs";
1757
- import { dirname, join } from "path";
1758
- import { fileURLToPath } from "url";
1759
- var PACKAGE_NAME = "@luxkit/cli";
1760
- var cachedVersion;
1761
- function getCurrentVersion() {
1762
- if (cachedVersion) return cachedVersion;
1763
- const __dirname = dirname(fileURLToPath(import.meta.url));
1764
- const candidates = [
1765
- join(__dirname, "..", "package.json"),
1766
- join(__dirname, "..", "..", "package.json")
1767
- ];
1768
- const pkgPath = candidates.find((p) => existsSync(p));
1769
- if (!pkgPath) {
1770
- throw new Error("Cannot locate package.json for version reading");
1771
- }
1772
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1773
- cachedVersion = pkg.version;
1774
- return cachedVersion;
1775
- }
1776
-
1777
- // src/commands/update.ts
1778
- var GLOBAL_UPDATE_CMDS = {
1779
- npm: ["npm", ["install", "-g", `${PACKAGE_NAME}@latest`]],
1780
- bun: ["bun", ["install", "-g", `${PACKAGE_NAME}@latest`]]
1781
- };
1782
- function detectGlobalPackageManager() {
1783
- return process.execPath.toLowerCase().includes("bun") ? "bun" : "npm";
1784
- }
1785
- async function fetchLatestVersion() {
1786
- const { stdout, exitCode } = await execFileNoThrow("npm", ["view", PACKAGE_NAME, "version"]);
1787
- if (exitCode !== 0 || !stdout) {
1788
- throw new Error(`Failed to fetch latest version from npm registry.`);
1789
- }
1790
- const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
1791
- return lines[lines.length - 1].trim();
1792
- }
1793
- async function performUpdate(pm) {
1794
- const [command, args] = GLOBAL_UPDATE_CMDS[pm];
1795
- const { exitCode, stderr } = await execFileNoThrow(command, args);
1796
- if (exitCode !== 0) {
1797
- const hint = /EACCES|permission/i.test(stderr) ? "\nTry running with elevated privileges (e.g. sudo)." : "";
1798
- throw new Error(`Update failed: ${stderr}${hint}`);
1799
- }
1800
- }
1801
- function registerUpdateCommand(program2) {
1802
- program2.command("update").description("Update @luxkit/cli to the latest version").option("--check", "Only check for updates without installing").action(async (options) => {
1803
- try {
1804
- const current = getCurrentVersion();
1805
- const latest = await fetchLatestVersion();
1806
- if (current === latest) {
1807
- logger.log(`Already up to date (v${current})`);
1808
- return;
1809
- }
1810
- if (options.check) {
1811
- logger.log(`Update available: v${current} \u2192 v${latest}`);
1812
- return;
1813
- }
1814
- const pm = detectGlobalPackageManager();
1815
- await performUpdate(pm);
1816
- logger.log(`Updated to v${latest}`);
1817
- } catch (err) {
1818
- const message = err instanceof Error ? err.message : String(err);
1819
- logger.error(message);
1820
- logger.warn(`You can also update manually: npm install -g ${PACKAGE_NAME}@latest`);
1821
- }
1822
- });
1823
- }
1824
-
1825
1687
  // src/presets/vscode/web-vue.ts
1826
1688
  var webVueVscode = {
1827
1689
  name: "web-vue",
@@ -2580,6 +2442,226 @@ var VSCODE_PRESETS = [
2580
2442
  goVscode
2581
2443
  ];
2582
2444
 
2445
+ // src/generators/init.ts
2446
+ import fs4 from "fs";
2447
+ import path6 from "path";
2448
+ function resolveSkillsDir() {
2449
+ const entryDir = path6.dirname(process.argv[1] ?? "");
2450
+ return path6.resolve(entryDir, "skills");
2451
+ }
2452
+ function listFilesRecursive(dir, base) {
2453
+ const entries = fs4.readdirSync(dir, { withFileTypes: true });
2454
+ const files = [];
2455
+ for (const entry of entries) {
2456
+ const childBase = `${base}/${entry.name}`;
2457
+ const fullPath = path6.join(dir, entry.name);
2458
+ if (entry.isDirectory()) {
2459
+ files.push(...listFilesRecursive(fullPath, childBase));
2460
+ } else {
2461
+ files.push(childBase);
2462
+ }
2463
+ }
2464
+ return files;
2465
+ }
2466
+ function generateInitSkills(targetBaseDir, cwd) {
2467
+ const skillsDir = resolveSkillsDir();
2468
+ if (!fs4.existsSync(skillsDir)) {
2469
+ logger.error(`Bundled skills directory not found: ${skillsDir}`);
2470
+ logger.error('Please run "lux build" or reinstall lux.');
2471
+ return { copiedFiles: [], targetDir: targetBaseDir };
2472
+ }
2473
+ const targetPath = path6.resolve(cwd, targetBaseDir);
2474
+ try {
2475
+ fs4.cpSync(skillsDir, targetPath, { recursive: true, force: true });
2476
+ } catch (error) {
2477
+ const message = error instanceof Error ? error.message : String(error);
2478
+ logger.error(`Failed to copy skills to ${targetPath}: ${message}`);
2479
+ return { copiedFiles: [], targetDir: targetBaseDir };
2480
+ }
2481
+ const copiedFiles = fs4.existsSync(targetPath) ? listFilesRecursive(targetPath, targetBaseDir) : [];
2482
+ return { copiedFiles, targetDir: targetBaseDir };
2483
+ }
2484
+
2485
+ // src/commands/init.ts
2486
+ function registerInitCommand(program2) {
2487
+ program2.command("init").description("Initialize skills or materialize presets").option("--preset", "Materialize all presets to ~/.lux/preset/ without writing to cwd").action(async (options) => {
2488
+ if (options.preset) {
2489
+ materializeAllPresets();
2490
+ return;
2491
+ }
2492
+ const toolOptions = INIT_TOOLS.map((tool2) => ({
2493
+ value: tool2.name,
2494
+ label: tool2.label
2495
+ }));
2496
+ const selected = await select({
2497
+ message: "Which AI coding tool do you use?",
2498
+ options: toolOptions
2499
+ });
2500
+ if (isCancel(selected)) {
2501
+ cancel("Operation cancelled.");
2502
+ return;
2503
+ }
2504
+ const tool = INIT_TOOLS.find((t) => t.name === selected);
2505
+ if (!tool) {
2506
+ logger.error(`Unknown tool: ${String(selected)}`);
2507
+ return;
2508
+ }
2509
+ const cwd = process.cwd();
2510
+ const result = generateInitSkills(tool.targetDir, cwd);
2511
+ if (result.copiedFiles.length === 0) {
2512
+ logger.warn("No skill files were copied.");
2513
+ return;
2514
+ }
2515
+ for (const file of result.copiedFiles) {
2516
+ logger.log(` ${file}`);
2517
+ }
2518
+ outro(`Skills installed to ${tool.targetDir}/`);
2519
+ });
2520
+ }
2521
+ function materializeAllPresets() {
2522
+ const opts = {
2523
+ cwd: process.cwd(),
2524
+ force: false,
2525
+ dryRun: false,
2526
+ noStylelint: false,
2527
+ noEditorconfig: false
2528
+ };
2529
+ for (const preset of FMT_PRESETS) {
2530
+ materializeFmtPreset(preset.name, preset, opts);
2531
+ }
2532
+ for (const preset of VSCODE_PRESETS) {
2533
+ materializeVscodePresetFromBuiltin(preset.name, preset);
2534
+ }
2535
+ logger.success("All presets materialized to ~/.lux/preset/");
2536
+ }
2537
+
2538
+ // src/utils/config.ts
2539
+ import fs5 from "fs";
2540
+ import os2 from "os";
2541
+ import path7 from "path";
2542
+ var CONFIG_DIR = ".lux";
2543
+ var ENV_FILE = "env.txt";
2544
+ function getEnvConfigPath() {
2545
+ return path7.join(os2.homedir(), CONFIG_DIR, ENV_FILE);
2546
+ }
2547
+ function getEnvConfig() {
2548
+ let content;
2549
+ try {
2550
+ content = fs5.readFileSync(getEnvConfigPath(), "utf-8");
2551
+ } catch {
2552
+ return {};
2553
+ }
2554
+ const result = {};
2555
+ for (const line of content.split("\n")) {
2556
+ const trimmed = line.trim();
2557
+ if (!trimmed || trimmed.startsWith("#")) continue;
2558
+ const eqIndex = trimmed.indexOf("=");
2559
+ if (eqIndex === -1) continue;
2560
+ const key = trimmed.slice(0, eqIndex).trim();
2561
+ const raw = trimmed.slice(eqIndex + 1).trim();
2562
+ const value = raw.replace(/^["']|["']$/g, "");
2563
+ if (key) result[key] = value;
2564
+ }
2565
+ return result;
2566
+ }
2567
+ function setEnvConfig(data) {
2568
+ const lines = Object.entries(data).filter(([, v]) => v !== "").map(([k, v]) => `${k}="${v}"`);
2569
+ writeFile(getEnvConfigPath(), lines.join("\n") + "\n");
2570
+ }
2571
+ function clearEnvConfig() {
2572
+ try {
2573
+ fs5.unlinkSync(getEnvConfigPath());
2574
+ } catch {
2575
+ }
2576
+ }
2577
+
2578
+ // src/commands/show.ts
2579
+ function handleShowEnv() {
2580
+ const config = getEnvConfig();
2581
+ const entries = Object.entries(config);
2582
+ if (entries.length === 0) {
2583
+ logger.log("No env config.");
2584
+ return;
2585
+ }
2586
+ for (const [key, value] of entries) {
2587
+ logger.log(`${key}="${value}"`);
2588
+ }
2589
+ }
2590
+ function registerShowCommand(program2) {
2591
+ const show = program2.command("show");
2592
+ show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
2593
+ }
2594
+
2595
+ // src/utils/version.ts
2596
+ import { existsSync, readFileSync } from "fs";
2597
+ import { dirname, join } from "path";
2598
+ import { fileURLToPath } from "url";
2599
+ var PACKAGE_NAME = "@luxkit/cli";
2600
+ var cachedVersion;
2601
+ function getCurrentVersion() {
2602
+ if (cachedVersion) return cachedVersion;
2603
+ const __dirname = dirname(fileURLToPath(import.meta.url));
2604
+ const candidates = [
2605
+ join(__dirname, "..", "package.json"),
2606
+ join(__dirname, "..", "..", "package.json")
2607
+ ];
2608
+ const pkgPath = candidates.find((p) => existsSync(p));
2609
+ if (!pkgPath) {
2610
+ throw new Error("Cannot locate package.json for version reading");
2611
+ }
2612
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
2613
+ cachedVersion = pkg.version;
2614
+ return cachedVersion;
2615
+ }
2616
+
2617
+ // src/commands/update.ts
2618
+ var GLOBAL_UPDATE_CMDS = {
2619
+ npm: ["npm", ["install", "-g", `${PACKAGE_NAME}@latest`]],
2620
+ bun: ["bun", ["install", "-g", `${PACKAGE_NAME}@latest`]]
2621
+ };
2622
+ function detectGlobalPackageManager() {
2623
+ return process.execPath.toLowerCase().includes("bun") ? "bun" : "npm";
2624
+ }
2625
+ async function fetchLatestVersion() {
2626
+ const { stdout, exitCode } = await execFileNoThrow("npm", ["view", PACKAGE_NAME, "version"]);
2627
+ if (exitCode !== 0 || !stdout) {
2628
+ throw new Error(`Failed to fetch latest version from npm registry.`);
2629
+ }
2630
+ const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
2631
+ return lines[lines.length - 1].trim();
2632
+ }
2633
+ async function performUpdate(pm) {
2634
+ const [command, args] = GLOBAL_UPDATE_CMDS[pm];
2635
+ const { exitCode, stderr } = await execFileNoThrow(command, args);
2636
+ if (exitCode !== 0) {
2637
+ const hint = /EACCES|permission/i.test(stderr) ? "\nTry running with elevated privileges (e.g. sudo)." : "";
2638
+ throw new Error(`Update failed: ${stderr}${hint}`);
2639
+ }
2640
+ }
2641
+ function registerUpdateCommand(program2) {
2642
+ program2.command("update").description("Update @luxkit/cli to the latest version").option("--check", "Only check for updates without installing").action(async (options) => {
2643
+ try {
2644
+ const current = getCurrentVersion();
2645
+ const latest = await fetchLatestVersion();
2646
+ if (current === latest) {
2647
+ logger.log(`Already up to date (v${current})`);
2648
+ return;
2649
+ }
2650
+ if (options.check) {
2651
+ logger.log(`Update available: v${current} \u2192 v${latest}`);
2652
+ return;
2653
+ }
2654
+ const pm = detectGlobalPackageManager();
2655
+ await performUpdate(pm);
2656
+ logger.log(`Updated to v${latest}`);
2657
+ } catch (err) {
2658
+ const message = err instanceof Error ? err.message : String(err);
2659
+ logger.error(message);
2660
+ logger.warn(`You can also update manually: npm install -g ${PACKAGE_NAME}@latest`);
2661
+ }
2662
+ });
2663
+ }
2664
+
2583
2665
  // src/generators/vscode.ts
2584
2666
  var STYLELINT_SETTINGS_PREFIXES2 = [
2585
2667
  "stylelint.",
@@ -2670,7 +2752,15 @@ function registerVscodeCommand(program2) {
2670
2752
  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(
2671
2753
  async (presetName, options) => {
2672
2754
  const preset = resolvePreset(VSCODE_PRESETS, presetName);
2673
- if (!preset) return;
2755
+ if (!preset) {
2756
+ const err = new PresetNotFoundError(
2757
+ presetName,
2758
+ VSCODE_PRESETS.map((p) => p.name)
2759
+ );
2760
+ logger.error(err.message);
2761
+ process.exitCode = 1;
2762
+ return;
2763
+ }
2674
2764
  const cwd = process.cwd();
2675
2765
  if (options.reset) {
2676
2766
  resetLocalPreset("vscode", presetName);