@luxkit/cli 1.0.8 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -40
- package/dist/index.js +1044 -153
- package/dist/skills/lux/skill.md +55 -0
- package/package.json +19 -7
package/dist/index.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
import { program } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/fmt.ts
|
|
7
|
-
import
|
|
7
|
+
import fs3 from "fs";
|
|
8
|
+
import path5 from "path";
|
|
8
9
|
|
|
9
10
|
// src/presets/fmt/electron-vue.ts
|
|
10
11
|
var electronVueFmt = {
|
|
@@ -114,8 +115,8 @@ trim_trailing_whitespace = false
|
|
|
114
115
|
]
|
|
115
116
|
},
|
|
116
117
|
scripts: {
|
|
117
|
-
lint: "eslint .",
|
|
118
|
-
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix',
|
|
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',
|
|
119
120
|
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
120
121
|
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
121
122
|
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
@@ -297,8 +298,8 @@ trim_trailing_whitespace = false
|
|
|
297
298
|
]
|
|
298
299
|
},
|
|
299
300
|
scripts: {
|
|
300
|
-
lint: "eslint .",
|
|
301
|
-
"lint:fix": 'eslint "src/**/*.{js,ts}" --fix',
|
|
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',
|
|
302
303
|
format: 'prettier --write "src/**/*.{ts,js,json}"',
|
|
303
304
|
"format:check": 'prettier --check "src/**/*.{ts,js,json}"',
|
|
304
305
|
cspell: 'cspell --gitignore "src/**/*"',
|
|
@@ -416,8 +417,8 @@ trim_trailing_whitespace = false
|
|
|
416
417
|
]
|
|
417
418
|
},
|
|
418
419
|
scripts: {
|
|
419
|
-
lint: "eslint .",
|
|
420
|
-
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix',
|
|
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',
|
|
421
422
|
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
422
423
|
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
423
424
|
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
@@ -431,6 +432,134 @@ trim_trailing_whitespace = false
|
|
|
431
432
|
}
|
|
432
433
|
};
|
|
433
434
|
|
|
435
|
+
// src/presets/fmt/web-react.ts
|
|
436
|
+
var webReactFmt = {
|
|
437
|
+
name: "web-react",
|
|
438
|
+
description: "React Web frontend (Vite + React + TypeScript)",
|
|
439
|
+
eslint: () => `import js from '@eslint/js'
|
|
440
|
+
import tseslint from 'typescript-eslint'
|
|
441
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
442
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
443
|
+
import globals from 'globals'
|
|
444
|
+
import prettierConfig from 'eslint-config-prettier'
|
|
445
|
+
|
|
446
|
+
export default tseslint.config(
|
|
447
|
+
{ ignores: ['dist'] },
|
|
448
|
+
{
|
|
449
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
450
|
+
files: ['**/*.{ts,tsx}'],
|
|
451
|
+
languageOptions: {
|
|
452
|
+
ecmaVersion: 2020,
|
|
453
|
+
globals: globals.browser,
|
|
454
|
+
},
|
|
455
|
+
plugins: {
|
|
456
|
+
'react-hooks': reactHooks,
|
|
457
|
+
'react-refresh': reactRefresh,
|
|
458
|
+
},
|
|
459
|
+
rules: {
|
|
460
|
+
...reactHooks.configs.recommended.rules,
|
|
461
|
+
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
|
462
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
463
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
464
|
+
},
|
|
465
|
+
},
|
|
466
|
+
prettierConfig,
|
|
467
|
+
)
|
|
468
|
+
`,
|
|
469
|
+
prettier: () => JSON.stringify(
|
|
470
|
+
{
|
|
471
|
+
semi: false,
|
|
472
|
+
singleQuote: true,
|
|
473
|
+
tabWidth: 2,
|
|
474
|
+
trailingComma: "all",
|
|
475
|
+
printWidth: 100,
|
|
476
|
+
endOfLine: "lf"
|
|
477
|
+
},
|
|
478
|
+
null,
|
|
479
|
+
2
|
|
480
|
+
) + "\n",
|
|
481
|
+
prettierIgnore: () => `node_modules/
|
|
482
|
+
<lockfile>
|
|
483
|
+
dist/
|
|
484
|
+
coverage/
|
|
485
|
+
`,
|
|
486
|
+
stylelint: () => `export default {
|
|
487
|
+
plugins: ['stylelint-order', '@stylistic/stylelint-plugin'],
|
|
488
|
+
extends: [
|
|
489
|
+
'stylelint-config-standard-scss',
|
|
490
|
+
'stylelint-config-recess-order',
|
|
491
|
+
],
|
|
492
|
+
rules: {
|
|
493
|
+
'selector-class-pattern': null,
|
|
494
|
+
'scss/dollar-variable-pattern': null,
|
|
495
|
+
'scss/percent-placeholder-pattern': null,
|
|
496
|
+
'scss/at-mixin-pattern': null,
|
|
497
|
+
'order/properties-order': null,
|
|
498
|
+
},
|
|
499
|
+
}
|
|
500
|
+
`,
|
|
501
|
+
stylelintIgnore: () => `node_modules/
|
|
502
|
+
dist/
|
|
503
|
+
`,
|
|
504
|
+
cspell: () => JSON.stringify(
|
|
505
|
+
{
|
|
506
|
+
$schema: "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
|
|
507
|
+
version: "0.2",
|
|
508
|
+
language: "en,en-US",
|
|
509
|
+
allowCompoundWords: true,
|
|
510
|
+
words: ["vite", "react", "zustand", "tanstack"]
|
|
511
|
+
},
|
|
512
|
+
null,
|
|
513
|
+
2
|
|
514
|
+
) + "\n",
|
|
515
|
+
editorconfig: () => `root = true
|
|
516
|
+
|
|
517
|
+
[*]
|
|
518
|
+
charset = utf-8
|
|
519
|
+
indent_style = space
|
|
520
|
+
indent_size = 2
|
|
521
|
+
end_of_line = lf
|
|
522
|
+
insert_final_newline = true
|
|
523
|
+
trim_trailing_whitespace = true
|
|
524
|
+
|
|
525
|
+
[*.md]
|
|
526
|
+
trim_trailing_whitespace = false
|
|
527
|
+
`,
|
|
528
|
+
dependencies: {
|
|
529
|
+
dev: [
|
|
530
|
+
"eslint",
|
|
531
|
+
"@eslint/js",
|
|
532
|
+
"typescript-eslint",
|
|
533
|
+
"eslint-plugin-react-hooks",
|
|
534
|
+
"eslint-plugin-react-refresh",
|
|
535
|
+
"eslint-config-prettier",
|
|
536
|
+
"globals",
|
|
537
|
+
"prettier",
|
|
538
|
+
"stylelint",
|
|
539
|
+
"stylelint-config-standard-scss",
|
|
540
|
+
"stylelint-order",
|
|
541
|
+
"stylelint-scss",
|
|
542
|
+
"@stylistic/stylelint-plugin",
|
|
543
|
+
"postcss-scss",
|
|
544
|
+
"cspell"
|
|
545
|
+
]
|
|
546
|
+
},
|
|
547
|
+
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"
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
434
563
|
// src/presets/fmt/web-vue.ts
|
|
435
564
|
var webVueFmt = {
|
|
436
565
|
name: "web-vue",
|
|
@@ -535,8 +664,8 @@ trim_trailing_whitespace = false
|
|
|
535
664
|
]
|
|
536
665
|
},
|
|
537
666
|
scripts: {
|
|
538
|
-
lint: "eslint .",
|
|
539
|
-
"lint:fix": 'eslint "src/**/*.{js,ts,vue}" --fix',
|
|
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',
|
|
540
669
|
format: 'prettier --write "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
541
670
|
"format:check": 'prettier --check "src/**/*.{ts,js,json,vue,css,scss}"',
|
|
542
671
|
stylelint: 'stylelint "src/**/*.{css,scss,vue}"',
|
|
@@ -551,7 +680,14 @@ trim_trailing_whitespace = false
|
|
|
551
680
|
};
|
|
552
681
|
|
|
553
682
|
// src/presets/fmt/index.ts
|
|
554
|
-
var FMT_PRESETS = [
|
|
683
|
+
var FMT_PRESETS = [
|
|
684
|
+
webVueFmt,
|
|
685
|
+
webReactFmt,
|
|
686
|
+
electronVueFmt,
|
|
687
|
+
uniappFmt,
|
|
688
|
+
nodeFmt,
|
|
689
|
+
nestFmt
|
|
690
|
+
];
|
|
555
691
|
|
|
556
692
|
// src/utils/logger.ts
|
|
557
693
|
import chalk from "chalk";
|
|
@@ -667,7 +803,10 @@ function readJson(filePath) {
|
|
|
667
803
|
try {
|
|
668
804
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
669
805
|
return JSON.parse(raw);
|
|
670
|
-
} catch {
|
|
806
|
+
} catch (error) {
|
|
807
|
+
if (error.code === "ENOENT") return null;
|
|
808
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
809
|
+
logger.error(`Failed to read or parse ${path.basename(filePath)}: ${message}`);
|
|
671
810
|
return null;
|
|
672
811
|
}
|
|
673
812
|
}
|
|
@@ -693,13 +832,20 @@ function generateConfigFile(preset, filename, content, opts) {
|
|
|
693
832
|
if (action === "skip") return "skipped";
|
|
694
833
|
if (opts.dryRun) return exists ? "overwritten" : "created";
|
|
695
834
|
const resolved = opts.lockfile ? content.replace(/<lockfile>/g, opts.lockfile) : content.replace(/<lockfile>\n?/g, "");
|
|
696
|
-
|
|
835
|
+
try {
|
|
836
|
+
writeFile(filepath, resolved);
|
|
837
|
+
} catch (error) {
|
|
838
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
839
|
+
logger.error(`Failed to write ${filename}: ${message}`);
|
|
840
|
+
return null;
|
|
841
|
+
}
|
|
697
842
|
return exists ? "overwritten" : "created";
|
|
698
843
|
}
|
|
699
844
|
function generateAllFmt(preset, opts) {
|
|
700
845
|
const result = { created: [], overwritten: [], skipped: [] };
|
|
701
846
|
for (const { filename, getContent } of CONFIG_FILES) {
|
|
702
847
|
if (opts.noStylelint && filename.includes("stylelint")) continue;
|
|
848
|
+
if (opts.noEditorconfig && filename === ".editorconfig") continue;
|
|
703
849
|
const content = getContent(preset);
|
|
704
850
|
if (content === void 0) continue;
|
|
705
851
|
const action = generateConfigFile(preset, filename, content, opts);
|
|
@@ -713,6 +859,29 @@ function generateAllFmt(preset, opts) {
|
|
|
713
859
|
// src/utils/deps.ts
|
|
714
860
|
import path3 from "path";
|
|
715
861
|
import { spawn } from "child_process";
|
|
862
|
+
|
|
863
|
+
// src/utils/execFileNoThrow.ts
|
|
864
|
+
import { exec } from "child_process";
|
|
865
|
+
import { promisify } from "util";
|
|
866
|
+
var execAsync = promisify(exec);
|
|
867
|
+
async function execFileNoThrow(command, args, options) {
|
|
868
|
+
const cmdStr = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
869
|
+
try {
|
|
870
|
+
const { stdout, stderr } = await execAsync(cmdStr, {
|
|
871
|
+
cwd: options?.cwd
|
|
872
|
+
});
|
|
873
|
+
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 };
|
|
874
|
+
} catch (err) {
|
|
875
|
+
const error = err;
|
|
876
|
+
return {
|
|
877
|
+
stdout: (error.stdout ?? "").trim(),
|
|
878
|
+
stderr: (error.stderr ?? "").trim(),
|
|
879
|
+
exitCode: error.code === "ENOENT" ? null : 1
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// src/utils/deps.ts
|
|
716
885
|
function detectPackageManager(cwd) {
|
|
717
886
|
if (fileExists(`${cwd}/bun.lockb`) || fileExists(`${cwd}/bun.lock`)) return "bun";
|
|
718
887
|
if (fileExists(`${cwd}/pnpm-lock.yaml`)) return "pnpm";
|
|
@@ -743,6 +912,37 @@ function getRunPrefix(pm) {
|
|
|
743
912
|
return "npm run";
|
|
744
913
|
}
|
|
745
914
|
}
|
|
915
|
+
async function fetchPackageVersion(pkg) {
|
|
916
|
+
const { stdout, exitCode } = await execFileNoThrow("npm", ["view", pkg, "version"]);
|
|
917
|
+
if (exitCode !== 0 || !stdout) {
|
|
918
|
+
throw new Error(`Failed to fetch version for "${pkg}" from npm registry.`);
|
|
919
|
+
}
|
|
920
|
+
const lines = stdout.split("\n").filter((line) => line.trim().length > 0);
|
|
921
|
+
return lines[lines.length - 1].trim();
|
|
922
|
+
}
|
|
923
|
+
async function addDepsToManifest(packages, cwd) {
|
|
924
|
+
const pkgPath = path3.join(cwd, "package.json");
|
|
925
|
+
const pkg = readJson(pkgPath);
|
|
926
|
+
if (!pkg) {
|
|
927
|
+
throw new Error("package.json not found");
|
|
928
|
+
}
|
|
929
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
930
|
+
const missing = packages.filter((p) => !devDeps[p]);
|
|
931
|
+
if (missing.length === 0) return [];
|
|
932
|
+
const results = await Promise.all(
|
|
933
|
+
missing.map(async (pkgName) => {
|
|
934
|
+
const version = await fetchPackageVersion(pkgName);
|
|
935
|
+
return { pkgName, version };
|
|
936
|
+
})
|
|
937
|
+
);
|
|
938
|
+
const updatedDevDeps = { ...devDeps };
|
|
939
|
+
for (const { pkgName, version } of results) {
|
|
940
|
+
updatedDevDeps[pkgName] = `^${version}`;
|
|
941
|
+
}
|
|
942
|
+
pkg.devDependencies = updatedDevDeps;
|
|
943
|
+
writeJson(pkgPath, pkg);
|
|
944
|
+
return results.map((r) => r.pkgName);
|
|
945
|
+
}
|
|
746
946
|
async function installDevDeps(packages, cwd, pm) {
|
|
747
947
|
const manager = pm ?? detectPackageManager(cwd);
|
|
748
948
|
const pkg = readJson(path3.join(cwd, "package.json"));
|
|
@@ -780,6 +980,347 @@ async function installDevDeps(packages, cwd, pm) {
|
|
|
780
980
|
}
|
|
781
981
|
}
|
|
782
982
|
|
|
983
|
+
// src/core/local-preset.ts
|
|
984
|
+
import fs2 from "fs";
|
|
985
|
+
import os from "os";
|
|
986
|
+
import path4 from "path";
|
|
987
|
+
|
|
988
|
+
// src/core/merge-settings.ts
|
|
989
|
+
var USER_PRIORITY_KEYS = /* @__PURE__ */ new Set([
|
|
990
|
+
// Cursor/animation
|
|
991
|
+
"editor.cursorBlinking",
|
|
992
|
+
"editor.cursorSmoothCaretAnimation",
|
|
993
|
+
"editor.renderWhitespace",
|
|
994
|
+
"editor.guides.indentation",
|
|
995
|
+
"editor.largeFileOptimizations",
|
|
996
|
+
// Theme/appearance
|
|
997
|
+
"workbench.iconTheme",
|
|
998
|
+
"workbench.colorTheme",
|
|
999
|
+
// Suggestions
|
|
1000
|
+
"editor.inlineSuggest.enabled",
|
|
1001
|
+
"editor.suggestSelection",
|
|
1002
|
+
"editor.acceptSuggestionOnEnter",
|
|
1003
|
+
"editor.bracketPairColorization.enabled",
|
|
1004
|
+
"editor.autoClosingBrackets",
|
|
1005
|
+
"editor.autoClosingOvertype"
|
|
1006
|
+
]);
|
|
1007
|
+
function mergeVscodeSettings(preset, existing) {
|
|
1008
|
+
const result = { ...existing };
|
|
1009
|
+
for (const [key, presetVal] of Object.entries(preset)) {
|
|
1010
|
+
const existingVal = existing[key];
|
|
1011
|
+
if (existingVal === void 0) {
|
|
1012
|
+
result[key] = presetVal;
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (USER_PRIORITY_KEYS.has(key)) {
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
if (isPlainObject(presetVal) && isPlainObject(existingVal)) {
|
|
1019
|
+
result[key] = mergeVscodeSettings(
|
|
1020
|
+
presetVal,
|
|
1021
|
+
existingVal
|
|
1022
|
+
);
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
result[key] = presetVal;
|
|
1026
|
+
}
|
|
1027
|
+
return result;
|
|
1028
|
+
}
|
|
1029
|
+
function isPlainObject(val) {
|
|
1030
|
+
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// src/core/local-preset.ts
|
|
1034
|
+
var CONFIG_GETTERS = [
|
|
1035
|
+
{ filename: "eslint.config.mjs", getContent: (p) => p.eslint?.() },
|
|
1036
|
+
{ filename: ".prettierrc", getContent: (p) => p.prettier?.() },
|
|
1037
|
+
{ filename: ".prettierignore", getContent: (p) => p.prettierIgnore?.() },
|
|
1038
|
+
{ filename: "stylelint.config.mjs", getContent: (p) => p.stylelint?.() },
|
|
1039
|
+
{ filename: ".stylelintignore", getContent: (p) => p.stylelintIgnore?.() },
|
|
1040
|
+
{ filename: "cspell.json", getContent: (p) => p.cspell?.() },
|
|
1041
|
+
{ filename: ".editorconfig", getContent: (p) => p.editorconfig?.() }
|
|
1042
|
+
];
|
|
1043
|
+
var STYLELINT_FILES = /* @__PURE__ */ new Set(["stylelint.config.mjs", ".stylelintignore"]);
|
|
1044
|
+
var EDITORCONFIG_FILE = ".editorconfig";
|
|
1045
|
+
var STYLELINT_SETTINGS_PREFIXES = [
|
|
1046
|
+
"stylelint.",
|
|
1047
|
+
"css.validate",
|
|
1048
|
+
"less.validate",
|
|
1049
|
+
"scss.validate"
|
|
1050
|
+
];
|
|
1051
|
+
var STYLELINT_DEPS = /* @__PURE__ */ new Set([
|
|
1052
|
+
"stylelint",
|
|
1053
|
+
"stylelint-config-standard-scss",
|
|
1054
|
+
"stylelint-order",
|
|
1055
|
+
"stylelint-scss",
|
|
1056
|
+
"@stylistic/stylelint-plugin",
|
|
1057
|
+
"postcss-html",
|
|
1058
|
+
"postcss-scss"
|
|
1059
|
+
]);
|
|
1060
|
+
var STYLELINT_EXTENSION = "stylelint.vscode-stylelint";
|
|
1061
|
+
function getLuxDir() {
|
|
1062
|
+
return process.env.LUX_HOME || path4.join(os.homedir(), ".lux");
|
|
1063
|
+
}
|
|
1064
|
+
function getLocalPresetDir(type, presetName) {
|
|
1065
|
+
if (!isValidPresetName(presetName)) {
|
|
1066
|
+
throw new Error(`Invalid preset name: "${presetName}"`);
|
|
1067
|
+
}
|
|
1068
|
+
return path4.join(getLuxDir(), "preset", type, presetName);
|
|
1069
|
+
}
|
|
1070
|
+
function isValidPresetName(name) {
|
|
1071
|
+
return name.length > 0 && !name.includes("/") && !name.includes("\\") && !name.includes("..");
|
|
1072
|
+
}
|
|
1073
|
+
function localPresetExists(type, presetName) {
|
|
1074
|
+
const dir = getLocalPresetDir(type, presetName);
|
|
1075
|
+
return fs2.existsSync(dir);
|
|
1076
|
+
}
|
|
1077
|
+
function resetLocalPreset(type, presetName) {
|
|
1078
|
+
const dir = getLocalPresetDir(type, presetName);
|
|
1079
|
+
if (fs2.existsSync(dir)) {
|
|
1080
|
+
fs2.rmSync(dir, { recursive: true, force: true });
|
|
1081
|
+
logger.log(`Reset local preset: ${dir}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function materializeFmtPreset(presetName, preset, opts) {
|
|
1085
|
+
if (opts.dryRun) {
|
|
1086
|
+
logger.log("[dry-run] Would materialize local preset to ~/.lux/preset/fmt/" + presetName);
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
const presetDir = getLocalPresetDir("fmt", presetName);
|
|
1090
|
+
ensureDir(presetDir);
|
|
1091
|
+
for (const { filename, getContent } of CONFIG_GETTERS) {
|
|
1092
|
+
const content = getContent(preset);
|
|
1093
|
+
if (content === void 0) continue;
|
|
1094
|
+
const resolved = opts.lockfile ? content.replace(/<lockfile>/g, opts.lockfile) : content.replace(/<lockfile>\n?/g, "");
|
|
1095
|
+
writeFile(path4.join(presetDir, filename), resolved);
|
|
1096
|
+
}
|
|
1097
|
+
const templatePkg = buildTemplatePackageJson(preset);
|
|
1098
|
+
writeJson(path4.join(presetDir, "package.json"), templatePkg);
|
|
1099
|
+
logger.log(`Local preset created at ${presetDir}`);
|
|
1100
|
+
}
|
|
1101
|
+
function materializeVscodePreset(cwd, presetName) {
|
|
1102
|
+
const presetDir = getLocalPresetDir("vscode", presetName);
|
|
1103
|
+
ensureDir(presetDir);
|
|
1104
|
+
const settingsSrc = path4.join(cwd, ".vscode", "settings.json");
|
|
1105
|
+
if (fileExists(settingsSrc)) {
|
|
1106
|
+
const content = fs2.readFileSync(settingsSrc, "utf-8");
|
|
1107
|
+
writeFile(path4.join(presetDir, "settings.json"), content);
|
|
1108
|
+
}
|
|
1109
|
+
const extensionsSrc = path4.join(cwd, ".vscode", "extensions.json");
|
|
1110
|
+
if (fileExists(extensionsSrc)) {
|
|
1111
|
+
const content = fs2.readFileSync(extensionsSrc, "utf-8");
|
|
1112
|
+
writeFile(path4.join(presetDir, "extensions.json"), content);
|
|
1113
|
+
}
|
|
1114
|
+
logger.log(`Local preset created at ${presetDir}`);
|
|
1115
|
+
}
|
|
1116
|
+
var InvalidPackageJsonError = class extends Error {
|
|
1117
|
+
constructor(filePath) {
|
|
1118
|
+
super(`package.json exists but is not valid JSON: ${filePath}`);
|
|
1119
|
+
this.filePath = filePath;
|
|
1120
|
+
}
|
|
1121
|
+
filePath;
|
|
1122
|
+
};
|
|
1123
|
+
function applyLocalFmtPreset(cwd, presetName, opts) {
|
|
1124
|
+
const result = {
|
|
1125
|
+
created: [],
|
|
1126
|
+
overwritten: [],
|
|
1127
|
+
skipped: [],
|
|
1128
|
+
scriptsAdded: 0,
|
|
1129
|
+
scriptsSkipped: 0
|
|
1130
|
+
};
|
|
1131
|
+
const presetDir = getLocalPresetDir("fmt", presetName);
|
|
1132
|
+
if (!fs2.existsSync(presetDir)) {
|
|
1133
|
+
logger.warn(`Local preset not found at ${presetDir}`);
|
|
1134
|
+
return result;
|
|
1135
|
+
}
|
|
1136
|
+
const projectPkgPath = path4.join(cwd, "package.json");
|
|
1137
|
+
if (fileExists(projectPkgPath)) {
|
|
1138
|
+
try {
|
|
1139
|
+
JSON.parse(fs2.readFileSync(projectPkgPath, "utf-8"));
|
|
1140
|
+
} catch {
|
|
1141
|
+
throw new InvalidPackageJsonError(projectPkgPath);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
const entries = fs2.readdirSync(presetDir).filter((name) => name !== "package.json" && fs2.statSync(path4.join(presetDir, name)).isFile());
|
|
1145
|
+
for (const filename of entries) {
|
|
1146
|
+
if (opts.noStylelint && STYLELINT_FILES.has(filename)) continue;
|
|
1147
|
+
if (opts.noEditorconfig && filename === EDITORCONFIG_FILE) continue;
|
|
1148
|
+
const destPath = path4.join(cwd, filename);
|
|
1149
|
+
const exists = fileExists(destPath);
|
|
1150
|
+
if (exists && !opts.force) {
|
|
1151
|
+
result.skipped.push(filename);
|
|
1152
|
+
if (opts.dryRun) {
|
|
1153
|
+
logger.log(`[dry-run] Skipped ${filename} (already exists)`);
|
|
1154
|
+
}
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
if (opts.dryRun) {
|
|
1158
|
+
(exists ? result.overwritten : result.created).push(filename);
|
|
1159
|
+
logger.log(`[dry-run] Would copy ${filename} from local preset`);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
const content = fs2.readFileSync(path4.join(presetDir, filename), "utf-8");
|
|
1163
|
+
writeFile(destPath, content);
|
|
1164
|
+
(exists ? result.overwritten : result.created).push(filename);
|
|
1165
|
+
}
|
|
1166
|
+
const templatePkg = readJson(path4.join(presetDir, "package.json"));
|
|
1167
|
+
const projectPkg = readJson(projectPkgPath);
|
|
1168
|
+
if (templatePkg && projectPkg) {
|
|
1169
|
+
const pm = fileExists(path4.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
|
|
1170
|
+
const merged = mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result);
|
|
1171
|
+
if (!opts.dryRun) {
|
|
1172
|
+
writeJson(projectPkgPath, merged);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
return result;
|
|
1176
|
+
}
|
|
1177
|
+
function applyLocalVscodePreset(cwd, presetName, opts) {
|
|
1178
|
+
const result = {
|
|
1179
|
+
created: [],
|
|
1180
|
+
overwritten: [],
|
|
1181
|
+
skipped: [],
|
|
1182
|
+
scriptsAdded: 0,
|
|
1183
|
+
scriptsSkipped: 0
|
|
1184
|
+
};
|
|
1185
|
+
const presetDir = getLocalPresetDir("vscode", presetName);
|
|
1186
|
+
if (!fs2.existsSync(presetDir)) {
|
|
1187
|
+
logger.warn(`Local preset not found at ${presetDir}`);
|
|
1188
|
+
return result;
|
|
1189
|
+
}
|
|
1190
|
+
const settingsSrc = path4.join(presetDir, "settings.json");
|
|
1191
|
+
if (fileExists(settingsSrc)) {
|
|
1192
|
+
const presetSettings = readJson(settingsSrc);
|
|
1193
|
+
const filteredSettings = opts.noStylelint ? filterStylelintSettings(presetSettings ?? {}) : presetSettings;
|
|
1194
|
+
if (filteredSettings) {
|
|
1195
|
+
const settingsDest = path4.join(cwd, ".vscode", "settings.json");
|
|
1196
|
+
const existingSettings = readJson(settingsDest);
|
|
1197
|
+
if (existingSettings) {
|
|
1198
|
+
if (opts.dryRun) {
|
|
1199
|
+
result.overwritten.push(".vscode/settings.json");
|
|
1200
|
+
logger.log("[dry-run] Would merge .vscode/settings.json from local preset");
|
|
1201
|
+
} else {
|
|
1202
|
+
const merged = mergeVscodeSettings(filteredSettings, existingSettings);
|
|
1203
|
+
writeJson(settingsDest, merged);
|
|
1204
|
+
result.overwritten.push(".vscode/settings.json");
|
|
1205
|
+
}
|
|
1206
|
+
} else {
|
|
1207
|
+
if (opts.dryRun) {
|
|
1208
|
+
result.created.push(".vscode/settings.json");
|
|
1209
|
+
logger.log("[dry-run] Would create .vscode/settings.json from local preset");
|
|
1210
|
+
} else {
|
|
1211
|
+
writeJson(settingsDest, filteredSettings);
|
|
1212
|
+
result.created.push(".vscode/settings.json");
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
const extensionsSrc = path4.join(presetDir, "extensions.json");
|
|
1218
|
+
if (fileExists(extensionsSrc)) {
|
|
1219
|
+
const extensionsData = readJson(extensionsSrc);
|
|
1220
|
+
if (extensionsData) {
|
|
1221
|
+
let presetRecommendations = extensionsData.recommendations ?? [];
|
|
1222
|
+
if (opts.noStylelint) {
|
|
1223
|
+
presetRecommendations = presetRecommendations.filter((ext) => ext !== STYLELINT_EXTENSION);
|
|
1224
|
+
}
|
|
1225
|
+
if (opts.dryRun) {
|
|
1226
|
+
result.created.push(".vscode/extensions.json");
|
|
1227
|
+
logger.log("[dry-run] Would create .vscode/extensions.json from local preset");
|
|
1228
|
+
} else {
|
|
1229
|
+
const extensionsDest = path4.join(cwd, ".vscode", "extensions.json");
|
|
1230
|
+
const existingExtensions = readJson(extensionsDest);
|
|
1231
|
+
const existingRecommendations = existingExtensions?.recommendations ?? [];
|
|
1232
|
+
const merged = [.../* @__PURE__ */ new Set([...existingRecommendations, ...presetRecommendations])];
|
|
1233
|
+
writeJson(extensionsDest, { recommendations: merged });
|
|
1234
|
+
result.created.push(".vscode/extensions.json");
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
return result;
|
|
1239
|
+
}
|
|
1240
|
+
function buildTemplatePackageJson(preset) {
|
|
1241
|
+
const deps = {};
|
|
1242
|
+
if (preset.dependencies?.dev) {
|
|
1243
|
+
for (const dep of preset.dependencies.dev) {
|
|
1244
|
+
deps[dep] = "<latest>";
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
const scripts = preset.scripts ? { ...preset.scripts } : void 0;
|
|
1248
|
+
const result = {};
|
|
1249
|
+
if (Object.keys(deps).length > 0) {
|
|
1250
|
+
result.devDependencies = deps;
|
|
1251
|
+
}
|
|
1252
|
+
if (scripts && Object.keys(scripts).length > 0) {
|
|
1253
|
+
result.scripts = scripts;
|
|
1254
|
+
}
|
|
1255
|
+
return result;
|
|
1256
|
+
}
|
|
1257
|
+
function mergeTemplateIntoProject(templatePkg, projectPkg, pm, opts, result) {
|
|
1258
|
+
const merged = { ...projectPkg };
|
|
1259
|
+
const prefix = pm ? getRunPrefix(pm) : "";
|
|
1260
|
+
if (templatePkg.devDependencies) {
|
|
1261
|
+
const existingDeps = merged.devDependencies ?? {};
|
|
1262
|
+
const newDeps = { ...existingDeps };
|
|
1263
|
+
for (const [dep, version] of Object.entries(templatePkg.devDependencies)) {
|
|
1264
|
+
if (opts.noStylelint && STYLELINT_DEPS.has(dep)) continue;
|
|
1265
|
+
if (opts.noEditorconfig && dep.includes("editorconfig")) continue;
|
|
1266
|
+
if (existingDeps[dep] === void 0 && version !== "<latest>") {
|
|
1267
|
+
newDeps[dep] = version;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
merged.devDependencies = newDeps;
|
|
1271
|
+
}
|
|
1272
|
+
if (templatePkg.scripts) {
|
|
1273
|
+
const existingScripts = merged.scripts ?? {};
|
|
1274
|
+
const newScripts = { ...existingScripts };
|
|
1275
|
+
for (const [key, value] of Object.entries(templatePkg.scripts)) {
|
|
1276
|
+
if (opts.noStylelint && key.startsWith("stylelint")) continue;
|
|
1277
|
+
const resolved = value.replace(/<pm>/g, prefix);
|
|
1278
|
+
if (existingScripts[key] !== void 0 && !opts.force) {
|
|
1279
|
+
result.scriptsSkipped++;
|
|
1280
|
+
if (opts.dryRun) {
|
|
1281
|
+
logger.log(`[dry-run] Skipped script "${key}" (already exists)`);
|
|
1282
|
+
} else {
|
|
1283
|
+
logger.log(`Skipped script "${key}" (already exists)`);
|
|
1284
|
+
}
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
if (opts.dryRun) {
|
|
1288
|
+
result.scriptsAdded++;
|
|
1289
|
+
logger.log(`[dry-run] Would add script "${key}"`);
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
newScripts[key] = resolved;
|
|
1293
|
+
result.scriptsAdded++;
|
|
1294
|
+
}
|
|
1295
|
+
merged.scripts = newScripts;
|
|
1296
|
+
}
|
|
1297
|
+
return merged;
|
|
1298
|
+
}
|
|
1299
|
+
function filterStylelintSettings(settings) {
|
|
1300
|
+
const filtered = Object.fromEntries(
|
|
1301
|
+
Object.entries(settings).filter(
|
|
1302
|
+
([key]) => !STYLELINT_SETTINGS_PREFIXES.some((prefix) => key.startsWith(prefix))
|
|
1303
|
+
)
|
|
1304
|
+
);
|
|
1305
|
+
if (typeof filtered["editor.codeActionsOnSave"] === "object" && filtered["editor.codeActionsOnSave"] !== null) {
|
|
1306
|
+
const actions = { ...filtered["editor.codeActionsOnSave"] };
|
|
1307
|
+
delete actions["source.fixAll.stylelint"];
|
|
1308
|
+
filtered["editor.codeActionsOnSave"] = actions;
|
|
1309
|
+
}
|
|
1310
|
+
return filtered;
|
|
1311
|
+
}
|
|
1312
|
+
function resolveLocalDeps(deps) {
|
|
1313
|
+
const packages = [];
|
|
1314
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
1315
|
+
if (version === "<latest>") {
|
|
1316
|
+
packages.push(name);
|
|
1317
|
+
} else {
|
|
1318
|
+
packages.push(`${name}@${version}`);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return packages;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
783
1324
|
// src/commands/fmt.ts
|
|
784
1325
|
function filterStylelintScripts(scripts) {
|
|
785
1326
|
const filtered = {};
|
|
@@ -794,52 +1335,35 @@ function isNotStylelintDep(dep) {
|
|
|
794
1335
|
if (dep === "postcss-html" || dep === "postcss-scss") return false;
|
|
795
1336
|
return true;
|
|
796
1337
|
}
|
|
1338
|
+
function isNotEditorconfigDep(dep) {
|
|
1339
|
+
return !dep.includes("editorconfig");
|
|
1340
|
+
}
|
|
797
1341
|
function registerFmtCommand(program2) {
|
|
798
1342
|
const fmt = program2.command("fmt").description("Initialize formatting config with preset");
|
|
799
|
-
fmt.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--no-install", "Skip dependency installation").option("--dry-run", "Preview without writing files").option("--
|
|
1343
|
+
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(
|
|
800
1344
|
async (presetName, options) => {
|
|
801
1345
|
const preset = resolvePreset(FMT_PRESETS, presetName);
|
|
802
1346
|
if (!preset) return;
|
|
803
1347
|
const cwd = process.cwd();
|
|
804
|
-
const
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
if (allFiles.length === 0 && result.skipped.length === 0) {
|
|
815
|
-
logger.warn("No files to generate for this preset");
|
|
816
|
-
return;
|
|
1348
|
+
const pkgPath = path5.join(cwd, "package.json");
|
|
1349
|
+
if (fileExists(pkgPath)) {
|
|
1350
|
+
try {
|
|
1351
|
+
JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
|
|
1352
|
+
} catch {
|
|
1353
|
+
logger.error(
|
|
1354
|
+
"package.json exists but is not valid JSON. Fix it first, then re-run this command."
|
|
1355
|
+
);
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
817
1358
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
warnMissingPackageJson(preset, options.install !== false);
|
|
821
|
-
return;
|
|
1359
|
+
if (options.reset) {
|
|
1360
|
+
resetLocalPreset("fmt", presetName);
|
|
822
1361
|
}
|
|
823
|
-
const
|
|
824
|
-
if (
|
|
825
|
-
await
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
const devDeps = opts.noStylelint ? preset.dependencies.dev.filter(isNotStylelintDep) : preset.dependencies.dev;
|
|
829
|
-
if (options.install === false) {
|
|
830
|
-
logger.log(`Dependencies: ${devDeps.join(", ")}`);
|
|
831
|
-
return;
|
|
832
|
-
}
|
|
833
|
-
if (opts.dryRun) {
|
|
834
|
-
logger.log(`[dry-run] Would install: ${devDeps.join(", ")}`);
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
try {
|
|
838
|
-
logger.log(`Installing dependencies with ${pm}...`);
|
|
839
|
-
await installDevDeps(devDeps, cwd, pm);
|
|
840
|
-
logger.success("Dependencies installed successfully");
|
|
841
|
-
} catch {
|
|
842
|
-
logger.warn("Dependency installation failed. You can install manually.");
|
|
1362
|
+
const useLocal = localPresetExists("fmt", presetName);
|
|
1363
|
+
if (useLocal) {
|
|
1364
|
+
await executeLocalPath(cwd, presetName, options);
|
|
1365
|
+
} else {
|
|
1366
|
+
await executeBuiltinPath(cwd, presetName, preset, options);
|
|
843
1367
|
}
|
|
844
1368
|
}
|
|
845
1369
|
);
|
|
@@ -849,6 +1373,139 @@ function registerFmtCommand(program2) {
|
|
|
849
1373
|
}
|
|
850
1374
|
});
|
|
851
1375
|
}
|
|
1376
|
+
async function executeLocalPath(cwd, presetName, options) {
|
|
1377
|
+
logger.log("Using local custom preset");
|
|
1378
|
+
const opts = {
|
|
1379
|
+
cwd,
|
|
1380
|
+
force: options.force ?? false,
|
|
1381
|
+
dryRun: options.dryRun ?? false,
|
|
1382
|
+
noStylelint: options.stylelint !== true,
|
|
1383
|
+
noEditorconfig: options.editorconfig !== true
|
|
1384
|
+
};
|
|
1385
|
+
let result;
|
|
1386
|
+
try {
|
|
1387
|
+
result = applyLocalFmtPreset(cwd, presetName, opts);
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
if (error instanceof InvalidPackageJsonError) {
|
|
1390
|
+
logger.error(
|
|
1391
|
+
"package.json exists but is not valid JSON. Fix it first, then re-run this command."
|
|
1392
|
+
);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
throw error;
|
|
1396
|
+
}
|
|
1397
|
+
const allFiles = [...result.created, ...result.overwritten];
|
|
1398
|
+
if (allFiles.length > 0 || result.skipped.length > 0) {
|
|
1399
|
+
logApplyResult(result);
|
|
1400
|
+
}
|
|
1401
|
+
if (result.scriptsAdded > 0 || result.scriptsSkipped > 0) {
|
|
1402
|
+
logger.log(
|
|
1403
|
+
`Added ${result.scriptsAdded} script${result.scriptsAdded > 1 ? "s" : ""} to package.json${result.scriptsSkipped > 0 ? ` (${result.scriptsSkipped} skipped)` : ""}`
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
const pm = fileExists(path5.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
|
|
1407
|
+
if (!pm) return;
|
|
1408
|
+
const templatePkgPath = path5.join(getLocalPresetDir("fmt", presetName), "package.json");
|
|
1409
|
+
const templatePkg = readJson(templatePkgPath);
|
|
1410
|
+
if (!templatePkg?.devDependencies) return;
|
|
1411
|
+
const depsToInstall = filterDeps(
|
|
1412
|
+
Object.keys(templatePkg.devDependencies),
|
|
1413
|
+
opts.noStylelint,
|
|
1414
|
+
opts.noEditorconfig
|
|
1415
|
+
);
|
|
1416
|
+
const projectPkgPath = path5.join(cwd, "package.json");
|
|
1417
|
+
const projectPkg = readJson(projectPkgPath);
|
|
1418
|
+
if (!projectPkg) return;
|
|
1419
|
+
const existingDeps = projectPkg.devDependencies ?? {};
|
|
1420
|
+
const missing = depsToInstall.filter((dep) => !existingDeps[dep]);
|
|
1421
|
+
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
|
+
}
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
if (opts.dryRun) {
|
|
1433
|
+
logger.log(`[dry-run] Would install: ${missing.join(", ")}`);
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
try {
|
|
1437
|
+
logger.log(`Installing dependencies with ${pm}...`);
|
|
1438
|
+
const resolved = resolveLocalDeps(
|
|
1439
|
+
Object.fromEntries(
|
|
1440
|
+
Object.entries(templatePkg.devDependencies).filter(([k]) => missing.includes(k))
|
|
1441
|
+
)
|
|
1442
|
+
);
|
|
1443
|
+
await installDevDeps(resolved, cwd, pm);
|
|
1444
|
+
logger.success("Dependencies installed successfully");
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1447
|
+
logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
async function executeBuiltinPath(cwd, presetName, preset, options) {
|
|
1451
|
+
const pm = fileExists(path5.join(cwd, "package.json")) ? detectPackageManager(cwd) : void 0;
|
|
1452
|
+
const opts = {
|
|
1453
|
+
cwd,
|
|
1454
|
+
force: options.force ?? false,
|
|
1455
|
+
dryRun: options.dryRun ?? false,
|
|
1456
|
+
noStylelint: options.stylelint !== true,
|
|
1457
|
+
noEditorconfig: options.editorconfig !== true,
|
|
1458
|
+
lockfile: pm ? getLockfileName(pm) : void 0
|
|
1459
|
+
};
|
|
1460
|
+
const result = generateAllFmt(preset, opts);
|
|
1461
|
+
const allFiles = [...result.created, ...result.overwritten];
|
|
1462
|
+
if (allFiles.length === 0 && result.skipped.length === 0) {
|
|
1463
|
+
logger.warn("No files to generate for this preset");
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
logGenerationResult(result, opts.dryRun);
|
|
1467
|
+
if (!opts.dryRun) {
|
|
1468
|
+
materializeFmtPreset(presetName, preset, opts);
|
|
1469
|
+
}
|
|
1470
|
+
if (!pm) {
|
|
1471
|
+
warnMissingPackageJson(preset, options.install !== false);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
const scripts = opts.noStylelint && preset.scripts ? filterStylelintScripts(preset.scripts) : preset.scripts;
|
|
1475
|
+
if (scripts) {
|
|
1476
|
+
await injectScripts(scripts, opts, pm);
|
|
1477
|
+
}
|
|
1478
|
+
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
|
+
}
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (opts.dryRun) {
|
|
1491
|
+
logger.log(`[dry-run] Would install: ${finalDeps.join(", ")}`);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
try {
|
|
1495
|
+
logger.log(`Installing dependencies with ${pm}...`);
|
|
1496
|
+
await installDevDeps(finalDeps, cwd, pm);
|
|
1497
|
+
logger.success("Dependencies installed successfully");
|
|
1498
|
+
} catch (error) {
|
|
1499
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1500
|
+
logger.warn(`Dependency installation failed: ${message}. You can install manually.`);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
function filterDeps(deps, noStylelint, noEditorconfig) {
|
|
1504
|
+
let filtered = deps;
|
|
1505
|
+
if (noStylelint) filtered = filtered.filter(isNotStylelintDep);
|
|
1506
|
+
if (noEditorconfig) filtered = filtered.filter(isNotEditorconfigDep);
|
|
1507
|
+
return filtered;
|
|
1508
|
+
}
|
|
852
1509
|
function logGenerationResult(result, dryRun) {
|
|
853
1510
|
const files = [...result.created, ...result.overwritten];
|
|
854
1511
|
if (dryRun) {
|
|
@@ -862,12 +1519,29 @@ function logGenerationResult(result, dryRun) {
|
|
|
862
1519
|
}
|
|
863
1520
|
if (result.created.length > 0) {
|
|
864
1521
|
logger.log(
|
|
865
|
-
`Created ${summarizeFiles(result.created)} config ${result.created.length} file${result.created.length > 1 ? "s" : ""}`
|
|
1522
|
+
`Created ${summarizeFiles(result.created)} config ${result.created.length} file${result.created.length > 1 ? "s" : ""}`
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
if (result.overwritten.length > 0) {
|
|
1526
|
+
logger.log(
|
|
1527
|
+
`Overwritten ${summarizeFiles(result.overwritten)} config ${result.overwritten.length} file${result.overwritten.length > 1 ? "s" : ""}`
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
if (result.skipped.length > 0) {
|
|
1531
|
+
logger.log(
|
|
1532
|
+
`Skipped ${result.skipped.length} file${result.skipped.length > 1 ? "s" : ""} (already exists)`
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
function logApplyResult(result) {
|
|
1537
|
+
if (result.created.length > 0) {
|
|
1538
|
+
logger.log(
|
|
1539
|
+
`Created ${summarizeFiles(result.created)} config ${result.created.length} file${result.created.length > 1 ? "s" : ""} from local preset`
|
|
866
1540
|
);
|
|
867
1541
|
}
|
|
868
1542
|
if (result.overwritten.length > 0) {
|
|
869
1543
|
logger.log(
|
|
870
|
-
`Overwritten ${summarizeFiles(result.overwritten)} config ${result.overwritten.length} file${result.overwritten.length > 1 ? "s" : ""}`
|
|
1544
|
+
`Overwritten ${summarizeFiles(result.overwritten)} config ${result.overwritten.length} file${result.overwritten.length > 1 ? "s" : ""} from local preset`
|
|
871
1545
|
);
|
|
872
1546
|
}
|
|
873
1547
|
if (result.skipped.length > 0) {
|
|
@@ -896,7 +1570,7 @@ function summarizeFiles(filenames) {
|
|
|
896
1570
|
return [...categories].join(", ");
|
|
897
1571
|
}
|
|
898
1572
|
async function injectScripts(scripts, opts, pm) {
|
|
899
|
-
const pkgPath =
|
|
1573
|
+
const pkgPath = path5.join(opts.cwd, "package.json");
|
|
900
1574
|
const pkg = readJson(pkgPath);
|
|
901
1575
|
if (!pkg) {
|
|
902
1576
|
logger.warn("package.json not found, skipping script injection");
|
|
@@ -931,19 +1605,109 @@ async function injectScripts(scripts, opts, pm) {
|
|
|
931
1605
|
}
|
|
932
1606
|
}
|
|
933
1607
|
|
|
1608
|
+
// src/commands/init.ts
|
|
1609
|
+
import { select, isCancel, cancel, outro } from "@clack/prompts";
|
|
1610
|
+
|
|
1611
|
+
// src/presets/init.ts
|
|
1612
|
+
var INIT_TOOLS = [
|
|
1613
|
+
{
|
|
1614
|
+
name: "claude",
|
|
1615
|
+
label: "Claude Code",
|
|
1616
|
+
targetDir: ".claude/skills"
|
|
1617
|
+
},
|
|
1618
|
+
{
|
|
1619
|
+
name: "opencode",
|
|
1620
|
+
label: "OpenCode",
|
|
1621
|
+
targetDir: ".opencode/skills"
|
|
1622
|
+
}
|
|
1623
|
+
];
|
|
1624
|
+
|
|
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
|
+
|
|
934
1698
|
// src/utils/config.ts
|
|
935
|
-
import
|
|
936
|
-
import
|
|
937
|
-
import
|
|
1699
|
+
import fs5 from "fs";
|
|
1700
|
+
import os2 from "os";
|
|
1701
|
+
import path7 from "path";
|
|
938
1702
|
var CONFIG_DIR = ".lux";
|
|
939
1703
|
var ENV_FILE = "env.txt";
|
|
940
1704
|
function getEnvConfigPath() {
|
|
941
|
-
return
|
|
1705
|
+
return path7.join(os2.homedir(), CONFIG_DIR, ENV_FILE);
|
|
942
1706
|
}
|
|
943
1707
|
function getEnvConfig() {
|
|
944
1708
|
let content;
|
|
945
1709
|
try {
|
|
946
|
-
content =
|
|
1710
|
+
content = fs5.readFileSync(getEnvConfigPath(), "utf-8");
|
|
947
1711
|
} catch {
|
|
948
1712
|
return {};
|
|
949
1713
|
}
|
|
@@ -966,7 +1730,7 @@ function setEnvConfig(data) {
|
|
|
966
1730
|
}
|
|
967
1731
|
function clearEnvConfig() {
|
|
968
1732
|
try {
|
|
969
|
-
|
|
1733
|
+
fs5.unlinkSync(getEnvConfigPath());
|
|
970
1734
|
} catch {
|
|
971
1735
|
}
|
|
972
1736
|
}
|
|
@@ -988,27 +1752,6 @@ function registerShowCommand(program2) {
|
|
|
988
1752
|
show.command("env").description("Display stored proxy environment variables").action(() => handleShowEnv());
|
|
989
1753
|
}
|
|
990
1754
|
|
|
991
|
-
// src/utils/execFileNoThrow.ts
|
|
992
|
-
import { exec } from "child_process";
|
|
993
|
-
import { promisify } from "util";
|
|
994
|
-
var execAsync = promisify(exec);
|
|
995
|
-
async function execFileNoThrow(command, args, options) {
|
|
996
|
-
const cmdStr = args.length > 0 ? `${command} ${args.join(" ")}` : command;
|
|
997
|
-
try {
|
|
998
|
-
const { stdout, stderr } = await execAsync(cmdStr, {
|
|
999
|
-
cwd: options?.cwd
|
|
1000
|
-
});
|
|
1001
|
-
return { stdout: stdout.trim(), stderr: stderr.trim(), exitCode: 0 };
|
|
1002
|
-
} catch (err) {
|
|
1003
|
-
const error = err;
|
|
1004
|
-
return {
|
|
1005
|
-
stdout: (error.stdout ?? "").trim(),
|
|
1006
|
-
stderr: (error.stderr ?? "").trim(),
|
|
1007
|
-
exitCode: error.code === "ENOENT" ? null : 1
|
|
1008
|
-
};
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
1755
|
// src/utils/version.ts
|
|
1013
1756
|
import { existsSync, readFileSync } from "fs";
|
|
1014
1757
|
import { dirname, join } from "path";
|
|
@@ -1206,6 +1949,138 @@ var webVueVscode = {
|
|
|
1206
1949
|
]
|
|
1207
1950
|
};
|
|
1208
1951
|
|
|
1952
|
+
// src/presets/vscode/web-react.ts
|
|
1953
|
+
var webReactVscode = {
|
|
1954
|
+
name: "web-react",
|
|
1955
|
+
description: "VSCode config for React Web",
|
|
1956
|
+
settings: () => ({
|
|
1957
|
+
// ===== Editor Preferences =====
|
|
1958
|
+
"editor.tabSize": 2,
|
|
1959
|
+
"editor.detectIndentation": false,
|
|
1960
|
+
"editor.insertSpaces": true,
|
|
1961
|
+
"editor.renderWhitespace": "selection",
|
|
1962
|
+
"editor.guides.indentation": true,
|
|
1963
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1964
|
+
"editor.formatOnSave": true,
|
|
1965
|
+
"editor.codeActionsOnSave": {
|
|
1966
|
+
"source.fixAll.eslint": "explicit",
|
|
1967
|
+
"source.fixAll.stylelint": "explicit",
|
|
1968
|
+
"source.organizeImports": "never"
|
|
1969
|
+
},
|
|
1970
|
+
// Cursor & Animation
|
|
1971
|
+
"editor.cursorBlinking": "expand",
|
|
1972
|
+
"editor.cursorSmoothCaretAnimation": "on",
|
|
1973
|
+
"editor.largeFileOptimizations": true,
|
|
1974
|
+
// Code Assistance
|
|
1975
|
+
"editor.inlineSuggest.enabled": true,
|
|
1976
|
+
"editor.suggestSelection": "recentlyUsedByPrefix",
|
|
1977
|
+
"editor.acceptSuggestionOnEnter": "smart",
|
|
1978
|
+
"editor.bracketPairColorization.enabled": true,
|
|
1979
|
+
"editor.autoClosingBrackets": "beforeWhitespace",
|
|
1980
|
+
"editor.autoClosingOvertype": "always",
|
|
1981
|
+
// ===== TypeScript =====
|
|
1982
|
+
"js/ts.inlayHints.enumMemberValues.enabled": true,
|
|
1983
|
+
"js/ts.preferences.preferTypeOnlyAutoImports": true,
|
|
1984
|
+
"js/ts.preferences.includePackageJsonAutoImports": "on",
|
|
1985
|
+
"js/ts.preferences.importModuleSpecifier": "relative",
|
|
1986
|
+
"js/ts.suggest.autoImports": true,
|
|
1987
|
+
"js/ts.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"],
|
|
1988
|
+
// ===== Language-specific Formatting =====
|
|
1989
|
+
"[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
1990
|
+
"[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
1991
|
+
"[scss]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
1992
|
+
"[typescript]": {
|
|
1993
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1994
|
+
"editor.formatOnSave": true
|
|
1995
|
+
},
|
|
1996
|
+
"[javascript]": {
|
|
1997
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1998
|
+
"editor.formatOnSave": true
|
|
1999
|
+
},
|
|
2000
|
+
"[typescriptreact]": {
|
|
2001
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
2002
|
+
"editor.formatOnSave": true
|
|
2003
|
+
},
|
|
2004
|
+
"[javascriptreact]": {
|
|
2005
|
+
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
2006
|
+
"editor.formatOnSave": true
|
|
2007
|
+
},
|
|
2008
|
+
"[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
2009
|
+
"[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },
|
|
2010
|
+
// ===== Terminal =====
|
|
2011
|
+
"terminal.integrated.cursorBlinking": true,
|
|
2012
|
+
"terminal.integrated.tabs.enabled": true,
|
|
2013
|
+
"terminal.integrated.scrollback": 1e4,
|
|
2014
|
+
// ===== File Exclusion =====
|
|
2015
|
+
"files.watcherExclude": {
|
|
2016
|
+
"**/.git/objects/**": true,
|
|
2017
|
+
"**/.git/subtree-cache/**": true,
|
|
2018
|
+
"**/.vscode/**": true,
|
|
2019
|
+
"**/node_modules/**": true,
|
|
2020
|
+
"**/tmp/**": true,
|
|
2021
|
+
"**/dist/**": true,
|
|
2022
|
+
"**/pnpm-lock.yaml": true,
|
|
2023
|
+
"**/package-lock.json": true,
|
|
2024
|
+
"**/bun.lock": true,
|
|
2025
|
+
"**/yarn.lock": true
|
|
2026
|
+
},
|
|
2027
|
+
"search.exclude": {
|
|
2028
|
+
"**/node_modules": true,
|
|
2029
|
+
"**/*.log": true,
|
|
2030
|
+
"**/*.log*": true,
|
|
2031
|
+
"**/dist": true,
|
|
2032
|
+
"**/.git": true,
|
|
2033
|
+
"**/.vscode": false,
|
|
2034
|
+
"**/tmp": true,
|
|
2035
|
+
node_modules: true,
|
|
2036
|
+
"**/pnpm-lock.yaml": true,
|
|
2037
|
+
"**/package-lock.json": true,
|
|
2038
|
+
"**/bun.lock": true,
|
|
2039
|
+
"**/yarn.lock": true
|
|
2040
|
+
},
|
|
2041
|
+
// ===== File Nesting =====
|
|
2042
|
+
"explorer.fileNesting.enabled": true,
|
|
2043
|
+
"explorer.fileNesting.expand": false,
|
|
2044
|
+
"explorer.fileNesting.patterns": {
|
|
2045
|
+
"package.json": "pnpm-lock.yaml,yarn.lock,bun.lock, .gitignore, .browserslistrc, .npmrc, cspell.json,README.md, LICENSE*,.editorconfig",
|
|
2046
|
+
"eslint.config.mjs": ".prettierignore, .prettierrc, .prettierrc.json, .editorconfig",
|
|
2047
|
+
"tsconfig.json": "tsconfig.*.json",
|
|
2048
|
+
"tailwind.config.js": "postcss.config.js",
|
|
2049
|
+
"vite.config.{js,ts}": "vite.*.{js,ts}",
|
|
2050
|
+
".env": ".env.*"
|
|
2051
|
+
},
|
|
2052
|
+
// ===== ESLint =====
|
|
2053
|
+
"eslint.validate": [
|
|
2054
|
+
"javascript",
|
|
2055
|
+
"typescript",
|
|
2056
|
+
"javascriptreact",
|
|
2057
|
+
"typescriptreact",
|
|
2058
|
+
"html",
|
|
2059
|
+
"markdown",
|
|
2060
|
+
"json",
|
|
2061
|
+
"jsonc",
|
|
2062
|
+
"json5"
|
|
2063
|
+
],
|
|
2064
|
+
// ===== Stylelint =====
|
|
2065
|
+
"stylelint.enable": true,
|
|
2066
|
+
"stylelint.validate": ["css", "scss"],
|
|
2067
|
+
"stylelint.snippet": ["css", "scss"],
|
|
2068
|
+
"css.validate": false,
|
|
2069
|
+
"less.validate": false,
|
|
2070
|
+
"scss.validate": false,
|
|
2071
|
+
// ===== CSpell =====
|
|
2072
|
+
"cSpell.language": "en"
|
|
2073
|
+
}),
|
|
2074
|
+
extensions: () => [
|
|
2075
|
+
"dbaeumer.vscode-eslint",
|
|
2076
|
+
"esbenp.prettier-vscode",
|
|
2077
|
+
"stylelint.vscode-stylelint",
|
|
2078
|
+
"mrmlnc.vscode-scss",
|
|
2079
|
+
"streetsidesoftware.code-spell-checker",
|
|
2080
|
+
"editorconfig.editorconfig"
|
|
2081
|
+
]
|
|
2082
|
+
};
|
|
2083
|
+
|
|
1209
2084
|
// src/presets/vscode/electron.ts
|
|
1210
2085
|
var electronVueVscode = {
|
|
1211
2086
|
name: "electron-vue",
|
|
@@ -1697,6 +2572,7 @@ var goVscode = {
|
|
|
1697
2572
|
// src/presets/vscode/index.ts
|
|
1698
2573
|
var VSCODE_PRESETS = [
|
|
1699
2574
|
webVueVscode,
|
|
2575
|
+
webReactVscode,
|
|
1700
2576
|
electronVueVscode,
|
|
1701
2577
|
uniappVscode,
|
|
1702
2578
|
nodeVscode,
|
|
@@ -1704,59 +2580,14 @@ var VSCODE_PRESETS = [
|
|
|
1704
2580
|
goVscode
|
|
1705
2581
|
];
|
|
1706
2582
|
|
|
1707
|
-
// src/core/merge-settings.ts
|
|
1708
|
-
var USER_PRIORITY_KEYS = /* @__PURE__ */ new Set([
|
|
1709
|
-
// Cursor/animation
|
|
1710
|
-
"editor.cursorBlinking",
|
|
1711
|
-
"editor.cursorSmoothCaretAnimation",
|
|
1712
|
-
"editor.renderWhitespace",
|
|
1713
|
-
"editor.guides.indentation",
|
|
1714
|
-
"editor.largeFileOptimizations",
|
|
1715
|
-
// Theme/appearance
|
|
1716
|
-
"workbench.iconTheme",
|
|
1717
|
-
"workbench.colorTheme",
|
|
1718
|
-
// Suggestions
|
|
1719
|
-
"editor.inlineSuggest.enabled",
|
|
1720
|
-
"editor.suggestSelection",
|
|
1721
|
-
"editor.acceptSuggestionOnEnter",
|
|
1722
|
-
"editor.bracketPairColorization.enabled",
|
|
1723
|
-
"editor.autoClosingBrackets",
|
|
1724
|
-
"editor.autoClosingOvertype"
|
|
1725
|
-
]);
|
|
1726
|
-
function mergeVscodeSettings(preset, existing) {
|
|
1727
|
-
const result = { ...existing };
|
|
1728
|
-
for (const [key, presetVal] of Object.entries(preset)) {
|
|
1729
|
-
const existingVal = existing[key];
|
|
1730
|
-
if (existingVal === void 0) {
|
|
1731
|
-
result[key] = presetVal;
|
|
1732
|
-
continue;
|
|
1733
|
-
}
|
|
1734
|
-
if (USER_PRIORITY_KEYS.has(key)) {
|
|
1735
|
-
continue;
|
|
1736
|
-
}
|
|
1737
|
-
if (isPlainObject(presetVal) && isPlainObject(existingVal)) {
|
|
1738
|
-
result[key] = mergeVscodeSettings(
|
|
1739
|
-
presetVal,
|
|
1740
|
-
existingVal
|
|
1741
|
-
);
|
|
1742
|
-
continue;
|
|
1743
|
-
}
|
|
1744
|
-
result[key] = presetVal;
|
|
1745
|
-
}
|
|
1746
|
-
return result;
|
|
1747
|
-
}
|
|
1748
|
-
function isPlainObject(val) {
|
|
1749
|
-
return typeof val === "object" && val !== null && !Array.isArray(val);
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
2583
|
// src/generators/vscode.ts
|
|
1753
|
-
var
|
|
2584
|
+
var STYLELINT_SETTINGS_PREFIXES2 = [
|
|
1754
2585
|
"stylelint.",
|
|
1755
2586
|
"css.validate",
|
|
1756
2587
|
"less.validate",
|
|
1757
2588
|
"scss.validate"
|
|
1758
2589
|
];
|
|
1759
|
-
var
|
|
2590
|
+
var STYLELINT_EXTENSION2 = "stylelint.vscode-stylelint";
|
|
1760
2591
|
function generateVscodeSettings(preset, opts) {
|
|
1761
2592
|
const settingsPath = `${opts.cwd}/.vscode/settings.json`;
|
|
1762
2593
|
if (opts.dryRun) {
|
|
@@ -1764,25 +2595,50 @@ function generateVscodeSettings(preset, opts) {
|
|
|
1764
2595
|
return existingSettings2 ? "overwritten" : "created";
|
|
1765
2596
|
}
|
|
1766
2597
|
const rawSettings = preset.settings();
|
|
1767
|
-
const presetSettings = opts.noStylelint ?
|
|
2598
|
+
const presetSettings = opts.noStylelint ? filterStylelintSettings2(rawSettings) : rawSettings;
|
|
1768
2599
|
const existingSettings = readJson(settingsPath);
|
|
1769
2600
|
if (existingSettings) {
|
|
1770
2601
|
const backupPath = `${settingsPath}.bak`;
|
|
1771
2602
|
if (!fileExists(backupPath)) {
|
|
1772
|
-
|
|
1773
|
-
|
|
2603
|
+
try {
|
|
2604
|
+
writeFile(backupPath, JSON.stringify(existingSettings, null, 2) + "\n");
|
|
2605
|
+
logger.log("Backed up .vscode/settings.json \u2192 settings.json.bak");
|
|
2606
|
+
} catch (error) {
|
|
2607
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2608
|
+
logger.warn(
|
|
2609
|
+
`Failed to backup .vscode/settings.json: ${message}. Continuing without backup.`
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
try {
|
|
2614
|
+
const merged = mergeVscodeSettings(presetSettings, existingSettings);
|
|
2615
|
+
writeJson(settingsPath, merged);
|
|
2616
|
+
} catch (error) {
|
|
2617
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2618
|
+
logger.error(`Failed to write .vscode/settings.json: ${msg}`);
|
|
2619
|
+
return null;
|
|
1774
2620
|
}
|
|
1775
|
-
const merged = mergeVscodeSettings(presetSettings, existingSettings);
|
|
1776
|
-
writeJson(settingsPath, merged);
|
|
1777
2621
|
return "overwritten";
|
|
1778
2622
|
}
|
|
1779
|
-
|
|
2623
|
+
try {
|
|
2624
|
+
writeJson(settingsPath, presetSettings);
|
|
2625
|
+
} catch (error) {
|
|
2626
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2627
|
+
logger.error(`Failed to write .vscode/settings.json: ${msg}`);
|
|
2628
|
+
return null;
|
|
2629
|
+
}
|
|
1780
2630
|
return "created";
|
|
1781
2631
|
}
|
|
1782
2632
|
function generateVscodeExtensions(preset, opts) {
|
|
1783
2633
|
if (opts.dryRun) return "created";
|
|
1784
|
-
const extensions = opts.noStylelint ? preset.extensions().filter((ext) => ext !==
|
|
1785
|
-
|
|
2634
|
+
const extensions = opts.noStylelint ? preset.extensions().filter((ext) => ext !== STYLELINT_EXTENSION2) : preset.extensions();
|
|
2635
|
+
try {
|
|
2636
|
+
writeJson(`${opts.cwd}/.vscode/extensions.json`, { recommendations: extensions });
|
|
2637
|
+
} catch (error) {
|
|
2638
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2639
|
+
logger.error(`Failed to write .vscode/extensions.json: ${msg}`);
|
|
2640
|
+
return null;
|
|
2641
|
+
}
|
|
1786
2642
|
return "created";
|
|
1787
2643
|
}
|
|
1788
2644
|
function generateAllVscode(preset, opts) {
|
|
@@ -1794,10 +2650,10 @@ function generateAllVscode(preset, opts) {
|
|
|
1794
2650
|
if (extAction === "created") result.created.push(".vscode/extensions.json");
|
|
1795
2651
|
return result;
|
|
1796
2652
|
}
|
|
1797
|
-
function
|
|
2653
|
+
function filterStylelintSettings2(settings) {
|
|
1798
2654
|
const filtered = Object.fromEntries(
|
|
1799
2655
|
Object.entries(settings).filter(
|
|
1800
|
-
([key]) => !
|
|
2656
|
+
([key]) => !STYLELINT_SETTINGS_PREFIXES2.some((prefix) => key.startsWith(prefix))
|
|
1801
2657
|
)
|
|
1802
2658
|
);
|
|
1803
2659
|
if (typeof filtered["editor.codeActionsOnSave"] === "object" && filtered["editor.codeActionsOnSave"] !== null) {
|
|
@@ -1811,28 +2667,20 @@ function filterStylelintSettings(settings) {
|
|
|
1811
2667
|
// src/commands/vscode.ts
|
|
1812
2668
|
function registerVscodeCommand(program2) {
|
|
1813
2669
|
const vscode = program2.command("vscode").description("Initialize VSCode config with preset");
|
|
1814
|
-
vscode.argument("<preset>").option("-F, --force", "Force overwrite existing files").option("--dry-run", "Preview without writing files").option("--
|
|
2670
|
+
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(
|
|
1815
2671
|
async (presetName, options) => {
|
|
1816
2672
|
const preset = resolvePreset(VSCODE_PRESETS, presetName);
|
|
1817
2673
|
if (!preset) return;
|
|
1818
2674
|
const cwd = process.cwd();
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
force: options.force ?? false,
|
|
1822
|
-
dryRun: options.dryRun ?? false,
|
|
1823
|
-
noStylelint: options.stylelint === false
|
|
1824
|
-
};
|
|
1825
|
-
const result = generateAllVscode(preset, opts);
|
|
1826
|
-
const files = [...result.created, ...result.overwritten];
|
|
1827
|
-
if (files.length === 0) {
|
|
1828
|
-
logger.warn("No files generated");
|
|
1829
|
-
return;
|
|
2675
|
+
if (options.reset) {
|
|
2676
|
+
resetLocalPreset("vscode", presetName);
|
|
1830
2677
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
2678
|
+
const useLocal = localPresetExists("vscode", presetName);
|
|
2679
|
+
if (useLocal) {
|
|
2680
|
+
executeVscodeLocalPath(cwd, presetName, options);
|
|
2681
|
+
} else {
|
|
2682
|
+
executeVscodeBuiltinPath(cwd, presetName, preset, options);
|
|
1834
2683
|
}
|
|
1835
|
-
logger.log(`Created ${files.join(", ")}`);
|
|
1836
2684
|
}
|
|
1837
2685
|
);
|
|
1838
2686
|
vscode.command("list").description("List available vscode presets").action(() => {
|
|
@@ -1841,6 +2689,48 @@ function registerVscodeCommand(program2) {
|
|
|
1841
2689
|
}
|
|
1842
2690
|
});
|
|
1843
2691
|
}
|
|
2692
|
+
function executeVscodeLocalPath(cwd, presetName, options) {
|
|
2693
|
+
logger.log("Using local custom preset");
|
|
2694
|
+
const opts = {
|
|
2695
|
+
cwd,
|
|
2696
|
+
force: options.force ?? false,
|
|
2697
|
+
dryRun: options.dryRun ?? false,
|
|
2698
|
+
noStylelint: options.stylelint !== true,
|
|
2699
|
+
noEditorconfig: false
|
|
2700
|
+
};
|
|
2701
|
+
const result = applyLocalVscodePreset(cwd, presetName, opts);
|
|
2702
|
+
const files = [...result.created, ...result.overwritten];
|
|
2703
|
+
if (files.length === 0) {
|
|
2704
|
+
logger.warn("No files generated");
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
if (opts.dryRun) {
|
|
2708
|
+
logger.log(`[dry-run] Would create ${files.join(", ")} from local preset`);
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2711
|
+
logger.log(`Created ${files.join(", ")} from local preset`);
|
|
2712
|
+
}
|
|
2713
|
+
function executeVscodeBuiltinPath(cwd, presetName, preset, options) {
|
|
2714
|
+
const opts = {
|
|
2715
|
+
cwd,
|
|
2716
|
+
force: options.force ?? false,
|
|
2717
|
+
dryRun: options.dryRun ?? false,
|
|
2718
|
+
noStylelint: options.stylelint !== true,
|
|
2719
|
+
noEditorconfig: false
|
|
2720
|
+
};
|
|
2721
|
+
const result = generateAllVscode(preset, opts);
|
|
2722
|
+
const files = [...result.created, ...result.overwritten];
|
|
2723
|
+
if (files.length === 0) {
|
|
2724
|
+
logger.warn("No files generated");
|
|
2725
|
+
return;
|
|
2726
|
+
}
|
|
2727
|
+
if (opts.dryRun) {
|
|
2728
|
+
logger.log(`[dry-run] Would create ${files.join(", ")}`);
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
logger.log(`Created ${files.join(", ")}`);
|
|
2732
|
+
materializeVscodePreset(cwd, presetName);
|
|
2733
|
+
}
|
|
1844
2734
|
|
|
1845
2735
|
// src/commands/vpn.ts
|
|
1846
2736
|
import { spawnSync } from "child_process";
|
|
@@ -1921,6 +2811,7 @@ function registerVpnCommand(program2) {
|
|
|
1921
2811
|
// src/index.ts
|
|
1922
2812
|
program.name("lux").description("One-click project formatting & VSCode config CLI").version(getCurrentVersion());
|
|
1923
2813
|
registerFmtCommand(program);
|
|
2814
|
+
registerInitCommand(program);
|
|
1924
2815
|
registerVscodeCommand(program);
|
|
1925
2816
|
registerVpnCommand(program);
|
|
1926
2817
|
registerShowCommand(program);
|