vite-plus 0.2.7 → 0.2.8

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.
Files changed (54) hide show
  1. package/LICENSE +5 -1
  2. package/bin/vpr +3 -2
  3. package/binding/index.cjs +193 -80
  4. package/binding/index.d.cts +99 -8
  5. package/dist/agent-CI79DuSy.js +600 -0
  6. package/dist/bin.js +1368 -13
  7. package/dist/{cli-truncate-BVOlIIPy.js → cli-truncate-Bg3RDXpi.js} +0 -2
  8. package/dist/client/bundledDevClient.mjs +1 -0
  9. package/dist/config/bin.js +13 -126
  10. package/dist/{constants-BsuzPowC.js → constants-BppDcRc1.js} +1 -1
  11. package/dist/create/bin.js +90 -77
  12. package/dist/{define-config-DLh9fE95.cjs → define-config-CEm_MtvJ.cjs} +1 -1
  13. package/dist/{define-config-C2KfLHpC.js → define-config-DPnHe1Mx.js} +2 -2
  14. package/dist/{define-config-eLQH9us2.d.ts → define-config-DWoAkNdp.d.ts} +16 -0
  15. package/dist/define-config.cjs +1 -1
  16. package/dist/define-config.d.ts +1 -1
  17. package/dist/define-config.js +1 -1
  18. package/dist/{dist-CKz3vcoG.js → dist-CSQWHI6y.js} +3 -8
  19. package/dist/dist-DT25H9pj.js +3 -0
  20. package/dist/{agent-CPwWJ4qf.js → editor-CGWdbLgD.js} +690 -579
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/{json-DiRs8ceZ.js → json-BU88uu6o.js} +2 -6
  25. package/dist/migration/bin.js +58 -48
  26. package/dist/{oxlint-plugin-config-Bxgc4cwF.js → oxlint-plugin-config-BHOzlwMw.js} +1 -1
  27. package/dist/oxlint-plugin.js +1 -1
  28. package/dist/pack-bin.js +1 -1
  29. package/dist/{package-Ds3uThLb.js → package-hV-77cBb.js} +2 -2
  30. package/dist/{tsconfig-Jia2vgMG.js → prompts-DYap08te.js} +32 -174
  31. package/dist/{resolve-vite-config-BiuLZnn8.js → resolve-vite-config-EWXclqvV.js} +1 -1
  32. package/dist/{rolldown-runtime-C7HZzL1F.js → rolldown-runtime-CMFfr-1z.js} +1 -1
  33. package/dist/staged/bin.js +20 -23
  34. package/dist/{help-YP84FSEz.js → terminal-CrqqK8WT.js} +39 -2
  35. package/dist/tsconfig-CVUKLfL8.js +151 -0
  36. package/dist/{tsgolint-path-B-yOos8p.js → tsgolint-path-eMZT-oea.js} +4 -2
  37. package/dist/tsgolint-path.js +1 -1
  38. package/dist/version.js +5 -5
  39. package/dist/versions.js +4 -4
  40. package/docs/config/index.md +13 -0
  41. package/docs/config/staged.md +2 -1
  42. package/docs/guide/commit-hooks.md +38 -20
  43. package/docs/guide/env.md +8 -4
  44. package/docs/guide/index.md +1 -1
  45. package/docs/guide/install.md +2 -0
  46. package/docs/guide/installer-env-vars.md +3 -3
  47. package/docs/guide/migrate.md +10 -2
  48. package/docs/guide/monorepo.md +94 -6
  49. package/docs/guide/troubleshooting.md +3 -2
  50. package/package.json +17 -23
  51. package/dist/dist-CtM2JRbM.js +0 -3
  52. package/dist/editor-BbT1lTNc.js +0 -675
  53. package/dist/terminal-uTv0ZaMr.js +0 -33
  54. package/dist/{log-update-DHZRyJ2m.js → log-update-CoW8Z4Dl.js} +1 -1
@@ -1,40 +1,26 @@
1
- import { r as __toESM } from "./rolldown-runtime-C7HZzL1F.js";
2
- import { a as VITEST_AGE_GATE_EXEMPT_PACKAGES, c as VITE_PLUS_OVERRIDE_PACKAGES, d as isForceOverrideMode, l as VITE_PLUS_VERSION, n as BASEURL_TSCONFIG_WARNING, o as VITEST_VERSION, s as VITE_PLUS_NAME } from "./constants-BsuzPowC.js";
3
- import { i as ensureVitePlusImportRuleDefaults, r as createDefaultVitePlusLintConfig } from "./oxlint-plugin-config-Bxgc4cwF.js";
4
- import { B as runCommandSilently, D as require_semver, E as PackageManager, M as multiselect, P as select, R as isCancel, V as require_cross_spawn, a as hasTypesToRewriteInTsconfig, b as scalarString, c as rewriteTypesInTsconfig, i as hasBaseUrlInTsconfig, j as log, k as confirm, l as cancelAndExit, n as findTsconfigFiles, o as hasVitestTypesInTsconfig, s as removeDeprecatedTsconfigFalseOption, v as editYamlFile, w as getSpinner, y as readYamlFile } from "./tsconfig-Jia2vgMG.js";
5
- import { a as writeJsonFile, i as readJsonFile, n as editJsonFile, r as isJsonFile } from "./json-DiRs8ceZ.js";
6
- import { t as require_dist } from "./dist-CKz3vcoG.js";
7
- import { n as detectPackageMetadata } from "./package-Ds3uThLb.js";
1
+ import { r as __toESM } from "./rolldown-runtime-CMFfr-1z.js";
2
+ import { a as VITEST_AGE_GATE_EXEMPT_PACKAGES, c as VITE_PLUS_OVERRIDE_PACKAGES, d as isForceOverrideMode, l as VITE_PLUS_VERSION, n as BASEURL_TSCONFIG_WARNING, o as VITEST_VERSION, s as VITE_PLUS_NAME } from "./constants-BppDcRc1.js";
3
+ import { i as ensureVitePlusImportRuleDefaults, r as createDefaultVitePlusLintConfig } from "./oxlint-plugin-config-BHOzlwMw.js";
4
+ import { A as isCancel, C as log, E as select, M as runCommandSilently, N as require_cross_spawn, d as readYamlFile, f as scalarString, g as getSpinner, t as cancelAndExit, u as editYamlFile, v as PackageManager, w as multiselect, x as confirm, y as require_semver } from "./prompts-DYap08te.js";
5
+ import { a as writeJsonFile, c as parse, i as readJsonFile, n as editJsonFile, o as applyEdits, r as isJsonFile, s as modify, t as detectFormattingOptions } from "./json-BU88uu6o.js";
6
+ import { t as require_dist } from "./dist-CSQWHI6y.js";
7
+ import { a as hasTypesToRewriteInTsconfig, c as rewriteTypesInTsconfig, i as hasBaseUrlInTsconfig, n as findTsconfigFiles, o as hasVitestTypesInTsconfig, s as removeDeprecatedTsconfigFalseOption } from "./tsconfig-CVUKLfL8.js";
8
+ import { n as detectPackageMetadata, r as getScopeFromPackageName } from "./package-hV-77cBb.js";
9
+ import { f as displayRelative, l as SUPPORTED_GIT_HOOK_NAMES, p as rulesDir, u as findUnsafeHookInstallPath } from "./agent-CI79DuSy.js";
8
10
  import { n as addMigrationWarning, t as addManualStep } from "./report-BS7ULx7A.js";
9
11
  import path, { posix, win32 } from "node:path";
10
- import { hasConfigKey, mergeJsonConfig, mergeTsdownConfig, rewriteEslint, rewriteImportsInDirectory, rewriteScripts, wrapLazyPlugins } from "../binding/index.js";
12
+ import { detectWorkspace, hasConfigKey, mergeJsonConfig, mergeTsdownConfig, rewriteEslint, rewriteImportsInDirectory, rewritePrettier, rewriteScripts, wrapLazyPlugins } from "../binding/index.js";
13
+ import { styleText } from "node:util";
11
14
  import * as actualFS from "node:fs";
12
15
  import fs from "node:fs";
13
- import { lstatSync, readdir, readdirSync, readlinkSync, realpathSync as realpathSync$1 } from "fs";
14
- import { styleText } from "node:util";
15
- import { fileURLToPath } from "node:url";
16
16
  import os from "node:os";
17
+ import { lstatSync as lstatSync$1, readdir, readdirSync, readlinkSync, realpathSync as realpathSync$1 } from "fs";
18
+ import { fileURLToPath } from "node:url";
17
19
  import "oxlint";
18
20
  import fsPromises, { lstat, readdir as readdir$1, readlink, realpath } from "node:fs/promises";
19
21
  import { EventEmitter } from "node:events";
20
22
  import Stream from "node:stream";
21
23
  import { StringDecoder } from "node:string_decoder";
22
- //#region src/utils/path.ts
23
- function findPkgRoot() {
24
- let dir = import.meta.dirname;
25
- while (dir !== path.dirname(dir)) {
26
- if (fs.existsSync(path.join(dir, "package.json"))) return dir;
27
- dir = path.dirname(dir);
28
- }
29
- return dir;
30
- }
31
- const pkgRoot = findPkgRoot();
32
- const templatesDir = path.join(pkgRoot, "templates");
33
- const rulesDir = path.join(pkgRoot, "rules");
34
- function displayRelative(to, from = process.cwd()) {
35
- return path.relative(from, to).replaceAll("\\", "/");
36
- }
37
- //#endregion
38
24
  //#region src/migration/migrator/shared.ts
39
25
  var import_semver = /* @__PURE__ */ __toESM(require_semver(), 1);
40
26
  const LINT_STAGED_JSON_CONFIG_FILES = [".lintstagedrc.json", ".lintstagedrc"];
@@ -743,6 +729,184 @@ async function promptEslintMigration(projectPath, interactive, packages) {
743
729
  return true;
744
730
  }
745
731
  //#endregion
732
+ //#region src/migration/migrator/prettier.ts
733
+ function detectPrettierProject(projectPath, packages) {
734
+ const packageJsonPath = path.join(projectPath, "package.json");
735
+ if (!fs.existsSync(packageJsonPath)) return { hasDependency: false };
736
+ const pkg = readJsonFile(packageJsonPath);
737
+ let hasDependency = !!(pkg.devDependencies?.prettier || pkg.dependencies?.prettier);
738
+ const configFile = detectConfigs(projectPath).prettierConfig;
739
+ if (!hasDependency && packages) for (const wp of packages) {
740
+ const pkgJsonPath = path.join(projectPath, wp.path, "package.json");
741
+ if (!fs.existsSync(pkgJsonPath)) continue;
742
+ const wpPkg = readJsonFile(pkgJsonPath);
743
+ if (wpPkg.devDependencies?.prettier || wpPkg.dependencies?.prettier) {
744
+ hasDependency = true;
745
+ break;
746
+ }
747
+ }
748
+ return {
749
+ hasDependency,
750
+ configFile
751
+ };
752
+ }
753
+ /**
754
+ * Run `vp fmt --migrate=prettier` step with graceful error handling.
755
+ * Returns true on success, false on failure.
756
+ */
757
+ async function runPrettierMigrateStep(vpBin, cwd, spinner, failMessage, manualHint) {
758
+ try {
759
+ const result = await runCommandSilently({
760
+ command: vpBin,
761
+ args: ["fmt", "--migrate=prettier"],
762
+ cwd,
763
+ envs: process.env
764
+ });
765
+ if (result.exitCode !== 0) {
766
+ spinner.stop(failMessage);
767
+ const stderr = result.stderr.toString().trim();
768
+ if (stderr) log.warn(`⚠ ${stderr}`);
769
+ log.info(manualHint);
770
+ return false;
771
+ }
772
+ return true;
773
+ } catch {
774
+ spinner.stop(failMessage);
775
+ log.info(manualHint);
776
+ return false;
777
+ }
778
+ }
779
+ async function migratePrettierToOxfmt(projectPath, interactive, prettierConfigFile, packages, options) {
780
+ const vpBin = process.env.VP_CLI_BIN ?? "vp";
781
+ const spinner = options?.silent ? {
782
+ start: () => {},
783
+ stop: () => {},
784
+ pause: () => {},
785
+ resume: () => {},
786
+ cancel: () => {},
787
+ error: () => {},
788
+ clear: () => {},
789
+ message: () => {},
790
+ isCancelled: false
791
+ } : getSpinner(interactive);
792
+ if (prettierConfigFile) {
793
+ let tempPrettierConfig;
794
+ if (prettierConfigFile === "package.json#prettier") {
795
+ const packageJsonPath = path.join(projectPath, "package.json");
796
+ const pkg = readJsonFile(packageJsonPath);
797
+ if (pkg.prettier) {
798
+ tempPrettierConfig = path.join(projectPath, ".prettierrc.json");
799
+ fs.writeFileSync(tempPrettierConfig, JSON.stringify(pkg.prettier, null, 2));
800
+ } else return true;
801
+ }
802
+ try {
803
+ spinner.start("Migrating Prettier config to Oxfmt...");
804
+ if (!await runPrettierMigrateStep(vpBin, projectPath, spinner, "Prettier migration failed", "You can run `vp fmt --migrate=prettier` manually later")) return false;
805
+ spinner.stop("Prettier config migrated to .oxfmtrc.json");
806
+ } finally {
807
+ if (tempPrettierConfig) try {
808
+ fs.unlinkSync(tempPrettierConfig);
809
+ } catch {}
810
+ }
811
+ }
812
+ if (options?.report) options.report.prettierMigrated = true;
813
+ deletePrettierConfigFiles(projectPath, options?.report, options?.silent);
814
+ rewritePrettierPackageJson(path.join(projectPath, "package.json"));
815
+ if (packages) for (const pkg of packages) rewritePrettierPackageJson(path.join(projectPath, pkg.path, "package.json"));
816
+ rewritePrettierLintStagedConfigFiles(projectPath, options?.report);
817
+ const prettierIgnorePath = path.join(projectPath, ".prettierignore");
818
+ if (fs.existsSync(prettierIgnorePath)) warnMigration(`${displayRelative(prettierIgnorePath)} found — Oxfmt supports .prettierignore, but using the \`ignorePatterns\` option is recommended.`, options?.report);
819
+ return true;
820
+ }
821
+ function deletePrettierConfigFiles(basePath, report, silent = false) {
822
+ const configs = detectConfigs(basePath);
823
+ if (configs.prettierConfig && configs.prettierConfig !== "package.json#prettier") {
824
+ const configPath = path.join(basePath, configs.prettierConfig);
825
+ if (fs.existsSync(configPath)) {
826
+ fs.unlinkSync(configPath);
827
+ if (report) report.removedConfigCount++;
828
+ if (!silent) log.success(`✔ Removed ${displayRelative(configPath)}`);
829
+ }
830
+ }
831
+ for (const file of PRETTIER_CONFIG_FILES) {
832
+ if (file === configs.prettierConfig) continue;
833
+ const configPath = path.join(basePath, file);
834
+ if (fs.existsSync(configPath)) {
835
+ fs.unlinkSync(configPath);
836
+ if (report) report.removedConfigCount++;
837
+ if (!silent) log.success(`✔ Removed ${displayRelative(configPath)}`);
838
+ }
839
+ }
840
+ editJsonFile(path.join(basePath, "package.json"), (pkg) => {
841
+ if (pkg.prettier) {
842
+ delete pkg.prettier;
843
+ return pkg;
844
+ }
845
+ });
846
+ }
847
+ function rewritePrettierPackageJson(packageJsonPath) {
848
+ if (!fs.existsSync(packageJsonPath)) return;
849
+ editJsonFile(packageJsonPath, (pkg) => {
850
+ let changed = false;
851
+ if (pkg.devDependencies) {
852
+ for (const dep of Object.keys(pkg.devDependencies)) if (dep === "prettier" || dep.startsWith("prettier-plugin-")) {
853
+ delete pkg.devDependencies[dep];
854
+ changed = true;
855
+ }
856
+ }
857
+ if (pkg.dependencies) {
858
+ for (const dep of Object.keys(pkg.dependencies)) if (dep === "prettier" || dep.startsWith("prettier-plugin-")) {
859
+ delete pkg.dependencies[dep];
860
+ changed = true;
861
+ }
862
+ }
863
+ if (pkg.scripts) {
864
+ const updated = rewritePrettier(JSON.stringify(pkg.scripts));
865
+ if (updated) {
866
+ pkg.scripts = JSON.parse(updated);
867
+ changed = true;
868
+ }
869
+ }
870
+ if (pkg["lint-staged"]) {
871
+ const updated = rewritePrettier(JSON.stringify(pkg["lint-staged"]));
872
+ if (updated) {
873
+ pkg["lint-staged"] = JSON.parse(updated);
874
+ changed = true;
875
+ }
876
+ }
877
+ return changed ? pkg : void 0;
878
+ });
879
+ }
880
+ function rewritePrettierLintStagedConfigFiles(projectPath, report) {
881
+ rewriteToolLintStagedConfigFiles(projectPath, rewritePrettier, "prettier", report);
882
+ }
883
+ function warnPackageLevelPrettier() {
884
+ log.warn("Prettier detected in workspace packages but no root config found. Package-level Prettier must be migrated manually.");
885
+ }
886
+ async function confirmPrettierMigration(interactive) {
887
+ if (interactive) {
888
+ const confirmed = await confirm({
889
+ message: "Migrate Prettier to Oxfmt?\n " + styleText("gray", "Oxfmt is Vite+'s built-in formatter that replaces Prettier with faster performance. Your configuration will be converted automatically."),
890
+ initialValue: true
891
+ });
892
+ if (isCancel(confirmed)) cancelAndExit();
893
+ return confirmed;
894
+ }
895
+ log.info("Prettier configuration detected. Auto-migrating to Oxfmt...");
896
+ return true;
897
+ }
898
+ async function promptPrettierMigration(projectPath, interactive, packages) {
899
+ const prettierProject = detectPrettierProject(projectPath, packages);
900
+ if (!prettierProject.hasDependency) return false;
901
+ if (!prettierProject.configFile) {
902
+ warnPackageLevelPrettier();
903
+ return false;
904
+ }
905
+ if (!await confirmPrettierMigration(interactive)) return false;
906
+ if (!await migratePrettierToOxfmt(projectPath, interactive, prettierProject.configFile, packages)) cancelAndExit("Prettier migration failed.", 1);
907
+ return true;
908
+ }
909
+ //#endregion
746
910
  //#region src/migration/migrator/tsconfig.ts
747
911
  function cleanupDeprecatedTsconfigOptions(projectPath, silent = false, report) {
748
912
  const deprecatedOptions = ["esModuleInterop", "allowSyntheticDefaultImports"];
@@ -767,6 +931,66 @@ function hasTsconfigTypesToRewrite(projectPath) {
767
931
  return findTsconfigFiles(projectPath).some((filePath) => hasTypesToRewriteInTsconfig(filePath));
768
932
  }
769
933
  //#endregion
934
+ //#region src/migration/migrator/framework-shim.ts
935
+ const FRAMEWORK_SHIMS = {
936
+ vue: [
937
+ "declare module '*.vue' {",
938
+ " import type { DefineComponent } from 'vue';",
939
+ " const component: DefineComponent<{}, {}, unknown>;",
940
+ " export default component;",
941
+ "}"
942
+ ].join("\n"),
943
+ astro: "/// <reference types=\"astro/client\" />"
944
+ };
945
+ function detectFramework(projectPath) {
946
+ const packageJsonPath = path.join(projectPath, "package.json");
947
+ if (!fs.existsSync(packageJsonPath)) return [];
948
+ const pkg = readJsonFile(packageJsonPath);
949
+ const allDeps = {
950
+ ...pkg.dependencies,
951
+ ...pkg.devDependencies
952
+ };
953
+ return ["vue", "astro"].filter((framework) => !!allDeps[framework]);
954
+ }
955
+ function getEnvDtsPath(projectPath) {
956
+ const srcEnvDts = path.join(projectPath, "src", "env.d.ts");
957
+ const rootEnvDts = path.join(projectPath, "env.d.ts");
958
+ for (const candidate of [srcEnvDts, rootEnvDts]) if (fs.existsSync(candidate)) return candidate;
959
+ return fs.existsSync(path.join(projectPath, "src")) ? srcEnvDts : rootEnvDts;
960
+ }
961
+ function hasFrameworkShim(projectPath, framework) {
962
+ const dirsToScan = [projectPath, path.join(projectPath, "src")];
963
+ for (const dir of dirsToScan) {
964
+ if (!fs.existsSync(dir)) continue;
965
+ let entries;
966
+ try {
967
+ entries = fs.readdirSync(dir);
968
+ } catch {
969
+ continue;
970
+ }
971
+ for (const entry of entries) {
972
+ if (!entry.endsWith(".d.ts")) continue;
973
+ const content = fs.readFileSync(path.join(dir, entry), "utf-8");
974
+ if (framework === "astro") {
975
+ if (content.includes("astro/client")) return true;
976
+ } else if (content.includes(`'*.${framework}'`) || content.includes(`"*.${framework}"`)) return true;
977
+ }
978
+ }
979
+ return false;
980
+ }
981
+ function addFrameworkShim(projectPath, framework, report) {
982
+ const envDtsPath = getEnvDtsPath(projectPath);
983
+ const shim = FRAMEWORK_SHIMS[framework];
984
+ if (fs.existsSync(envDtsPath)) {
985
+ const existing = fs.readFileSync(envDtsPath, "utf-8");
986
+ fs.writeFileSync(envDtsPath, `${existing.trimEnd()}\n\n${shim}\n`, "utf-8");
987
+ } else {
988
+ fs.mkdirSync(path.dirname(envDtsPath), { recursive: true });
989
+ fs.writeFileSync(envDtsPath, `${shim}\n`, "utf-8");
990
+ }
991
+ if (report) report.frameworkShimAdded = true;
992
+ }
993
+ //#endregion
770
994
  //#region src/migration/migrator/vitest-ecosystem.ts
771
995
  var import_dist = require_dist();
772
996
  const VITEST_ALIGN_EXCLUDED = /* @__PURE__ */ new Set(["@vitest/eslint-plugin", "@vitest/coverage-c8"]);
@@ -1251,7 +1475,7 @@ function expand_(str, max, isTop) {
1251
1475
  }
1252
1476
  //#endregion
1253
1477
  //#region ../../node_modules/.pnpm/minimatch@10.2.4/node_modules/minimatch/dist/esm/assert-valid-pattern.js
1254
- const MAX_PATTERN_LENGTH = 1024 * 64;
1478
+ const MAX_PATTERN_LENGTH = 65536;
1255
1479
  const assertValidPattern = (pattern) => {
1256
1480
  if (typeof pattern !== "string") throw new TypeError("invalid pattern");
1257
1481
  if (pattern.length > MAX_PATTERN_LENGTH) throw new TypeError("pattern is too long");
@@ -2556,7 +2780,6 @@ minimatch.escape = escape;
2556
2780
  minimatch.unescape = unescape;
2557
2781
  //#endregion
2558
2782
  //#region ../../node_modules/.pnpm/lru-cache@11.2.7/node_modules/lru-cache/dist/esm/index.min.js
2559
- var import_cross_spawn = /* @__PURE__ */ __toESM(require_cross_spawn(), 1);
2560
2783
  var x = typeof performance == "object" && performance && typeof performance.now == "function" ? performance : Date;
2561
2784
  var I = /* @__PURE__ */ new Set();
2562
2785
  var R = typeof process == "object" && process ? process : {};
@@ -3982,12 +4205,15 @@ while (this[FLUSHCHUNK](this[BUFFERSHIFT]()) && this[BUFFER].length);
3982
4205
  return isStream;
3983
4206
  }
3984
4207
  };
4208
+ //#endregion
4209
+ //#region ../../node_modules/.pnpm/path-scurry@2.0.1/node_modules/path-scurry/dist/esm/index.js
4210
+ const realpathSync$2 = realpathSync$1.native;
3985
4211
  const defaultFS = {
3986
- lstatSync,
4212
+ lstatSync: lstatSync$1,
3987
4213
  readdir,
3988
4214
  readdirSync,
3989
4215
  readlinkSync,
3990
- realpathSync: realpathSync$1.native,
4216
+ realpathSync: realpathSync$2,
3991
4217
  promises: {
3992
4218
  lstat,
3993
4219
  readdir: readdir$1,
@@ -4055,7 +4281,7 @@ var ResolveCache = class extends L {
4055
4281
  * @internal
4056
4282
  */
4057
4283
  var ChildrenCache = class extends L {
4058
- constructor(maxSize = 16 * 1024) {
4284
+ constructor(maxSize = 16384) {
4059
4285
  super({
4060
4286
  maxSize,
4061
4287
  sizeCalculation: (a) => a.length + 1
@@ -4388,7 +4614,7 @@ var PathBase = class {
4388
4614
  return this[`is${type}`]();
4389
4615
  }
4390
4616
  getType() {
4391
- return this.isUnknown() ? "Unknown" : this.isDirectory() ? "Directory" : this.isFile() ? "File" : this.isSymbolicLink() ? "SymbolicLink" : this.isFIFO() ? "FIFO" : this.isCharacterDevice() ? "CharacterDevice" : this.isBlockDevice() ? "BlockDevice" : /* c8 ignore start */ this.isSocket() ? "Socket" : "Unknown";
4617
+ return this.isUnknown() ? "Unknown" : this.isDirectory() ? "Directory" : this.isFile() ? "File" : this.isSymbolicLink() ? "SymbolicLink" : this.isFIFO() ? "FIFO" : this.isCharacterDevice() ? "CharacterDevice" : this.isBlockDevice() ? "BlockDevice" : /* c8 ignore start */ this.isSocket() ? "Socket" : "Unknown";
4392
4618
  /* c8 ignore stop */
4393
4619
  }
4394
4620
  /**
@@ -5013,7 +5239,7 @@ var PathScurryBase = class {
5013
5239
  *
5014
5240
  * @internal
5015
5241
  */
5016
- constructor(cwd = process.cwd(), pathImpl, sep, { nocase, childrenCacheSize = 16 * 1024, fs = defaultFS } = {}) {
5242
+ constructor(cwd = process.cwd(), pathImpl, sep, { nocase, childrenCacheSize = 16384, fs = defaultFS } = {}) {
5017
5243
  this.#fs = fsFromOption(fs);
5018
5244
  if (cwd instanceof URL || cwd.startsWith("file://")) cwd = fileURLToPath(cwd);
5019
5245
  const cwdPath = pathImpl.resolve(cwd);
@@ -6504,6 +6730,96 @@ const glob = Object.assign(glob_, {
6504
6730
  });
6505
6731
  glob.glob = glob;
6506
6732
  //#endregion
6733
+ //#region src/utils/workspace.ts
6734
+ async function detectWorkspace$1(rootDir) {
6735
+ const bindingResult = await detectWorkspace(rootDir);
6736
+ const result = {
6737
+ rootDir,
6738
+ packageManager: void 0,
6739
+ packageManagerVersion: "latest",
6740
+ isMonorepo: false,
6741
+ monorepoScope: "",
6742
+ workspacePatterns: [],
6743
+ parentDirs: [],
6744
+ packages: []
6745
+ };
6746
+ if (bindingResult.packageManagerName) result.packageManager = bindingResult.packageManagerName;
6747
+ if (bindingResult.packageManagerVersion) result.packageManagerVersion = bindingResult.packageManagerVersion;
6748
+ if (bindingResult.isMonorepo) result.isMonorepo = bindingResult.isMonorepo;
6749
+ if (bindingResult.root) result.rootDir = bindingResult.root;
6750
+ if (result.isMonorepo) {
6751
+ const pnpmWorkspaceFile = path.join(result.rootDir, "pnpm-workspace.yaml");
6752
+ const packageJsonFile = path.join(result.rootDir, "package.json");
6753
+ if (fs.existsSync(pnpmWorkspaceFile)) {
6754
+ const workspaceConfig = readYamlFile(pnpmWorkspaceFile);
6755
+ if (Array.isArray(workspaceConfig.packages)) result.workspacePatterns = workspaceConfig.packages;
6756
+ } else if (fs.existsSync(packageJsonFile)) {
6757
+ const pkg = readJsonFile(packageJsonFile);
6758
+ if (Array.isArray(pkg.workspaces)) result.workspacePatterns = pkg.workspaces;
6759
+ else if (pkg.workspaces && Array.isArray(pkg.workspaces.packages)) result.workspacePatterns = pkg.workspaces.packages;
6760
+ }
6761
+ const dirs = /* @__PURE__ */ new Set();
6762
+ for (const pattern of result.workspacePatterns) {
6763
+ if (!pattern.endsWith("*")) continue;
6764
+ const dir = pattern.replace(/\/\*{1,2}$/, "");
6765
+ if (dir) dirs.add(dir);
6766
+ }
6767
+ result.parentDirs = Array.from(dirs).sort();
6768
+ const pkg = readJsonFile(packageJsonFile);
6769
+ if (pkg.name) result.monorepoScope = getScopeFromPackageName(pkg.name);
6770
+ result.packages = discoverWorkspacePackages(result.workspacePatterns, result.rootDir);
6771
+ }
6772
+ return result;
6773
+ }
6774
+ function isBingoTemplate(pkg) {
6775
+ return !!pkg.dependencies?.bingo;
6776
+ }
6777
+ function discoverWorkspacePackages(workspacePatterns, rootDir) {
6778
+ const packages = [];
6779
+ if (workspacePatterns.length === 0) return packages;
6780
+ const packageJsonRelativePaths = globSync(workspacePatterns.map((pattern) => `${pattern}/package.json`), {
6781
+ absolute: false,
6782
+ cwd: rootDir,
6783
+ ignore: ["**/node_modules/**"]
6784
+ });
6785
+ for (const packageJsonRelativePath of packageJsonRelativePaths) {
6786
+ const packageJsonPath = path.join(rootDir, packageJsonRelativePath);
6787
+ const pkg = readJsonFile(packageJsonPath);
6788
+ if (!pkg.name) continue;
6789
+ packages.push({
6790
+ name: pkg.name,
6791
+ path: path.dirname(packageJsonRelativePath).split(path.sep).join("/"),
6792
+ description: pkg.description,
6793
+ version: pkg.version
6794
+ });
6795
+ }
6796
+ return packages;
6797
+ }
6798
+ function updatePackageJsonWithDeps(rootDir, projectDir, dependencies, dependencyType) {
6799
+ const packageJsonPath = path.join(rootDir, projectDir, "package.json");
6800
+ editJsonFile(packageJsonPath, (pkg) => {
6801
+ if (!pkg[dependencyType]) pkg[dependencyType] = {};
6802
+ for (const dep of dependencies) pkg[dependencyType][dep] = "workspace:*";
6803
+ return pkg;
6804
+ });
6805
+ }
6806
+ function updateWorkspaceConfig(projectPath, workspaceInfo) {
6807
+ for (const pattern of workspaceInfo.workspacePatterns) if (minimatch(projectPath, pattern)) return;
6808
+ const parentDir = path.dirname(projectPath);
6809
+ const pattern = parentDir === "." ? projectPath : `${parentDir}/*`;
6810
+ if (workspaceInfo.packageManager === PackageManager.pnpm) editYamlFile(path.join(workspaceInfo.rootDir, "pnpm-workspace.yaml"), (doc) => {
6811
+ let packages = doc.getIn(["packages"]);
6812
+ if (!packages) packages = new import_dist.YAMLSeq();
6813
+ packages.add(new import_dist.Scalar(pattern));
6814
+ doc.setIn(["packages"], packages);
6815
+ });
6816
+ else editJsonFile(path.join(workspaceInfo.rootDir, "package.json"), (pkg) => {
6817
+ if (pkg.workspaces && !Array.isArray(pkg.workspaces)) pkg.workspaces.packages = [...pkg.workspaces.packages || [], pattern];
6818
+ else pkg.workspaces = [...pkg.workspaces || [], pattern];
6819
+ return pkg;
6820
+ });
6821
+ }
6822
+ //#endregion
6507
6823
  //#region src/migration/migrator/catalog.ts
6508
6824
  const BROWSER_PROVIDER_POSTINSTALL_PACKAGES = ["edgedriver", "geckodriver"];
6509
6825
  const PUBLIC_PEER_DEPENDENCY_FALLBACKS = {
@@ -6684,7 +7000,8 @@ function rewritePnpmWorkspaceYaml(projectPath, pnpmMajorVersion, shouldAllowBrow
6684
7000
  * so all overrides must live in pnpm-workspace.yaml.
6685
7001
  */
6686
7002
  function migratePnpmOverridesToWorkspaceYaml(projectPath, overrides) {
6687
- editYamlFile(path.join(projectPath, "pnpm-workspace.yaml"), (doc) => {
7003
+ const pnpmWorkspaceYamlPath = path.join(projectPath, "pnpm-workspace.yaml");
7004
+ editYamlFile(pnpmWorkspaceYamlPath, (doc) => {
6688
7005
  for (const [key, value] of Object.entries(overrides)) doc.setIn(["overrides", scalarString(key)], scalarString(value));
6689
7006
  });
6690
7007
  }
@@ -8143,7 +8460,8 @@ function mergeTsdownConfigFile(projectPath, silent = false, report) {
8143
8460
  mergeAndRemoveJsonConfig(projectPath, viteConfig, configs.tsdownConfig, "pack", silent, report);
8144
8461
  return;
8145
8462
  }
8146
- const result = mergeTsdownConfig(fullViteConfigPath, `./${configs.tsdownConfig}`);
8463
+ const tsdownRelativePath = `./${configs.tsdownConfig}`;
8464
+ const result = mergeTsdownConfig(fullViteConfigPath, tsdownRelativePath);
8147
8465
  if (result.updated) {
8148
8466
  fs.writeFileSync(fullViteConfigPath, result.content);
8149
8467
  if (report) report.tsdownImportCount++;
@@ -8168,7 +8486,8 @@ function mergeViteConfigFiles(projectPath, silent = false, report, packages, wor
8168
8486
  } else warnMigration(BASEURL_TSCONFIG_WARNING, report);
8169
8487
  sanitizeMigratedOxlintConfig(oxlintJson, collectInstalledPackageNames(workspaceRoot ?? projectPath, packages), report);
8170
8488
  ensureSvelteRuneGlobals(oxlintJson);
8171
- writeJsonFile(fullOxlintPath, ensureVitePlusImportRuleDefaults(oxlintJson));
8489
+ const normalizedOxlintConfig = ensureVitePlusImportRuleDefaults(oxlintJson);
8490
+ writeJsonFile(fullOxlintPath, normalizedOxlintConfig);
8172
8491
  mergeAndRemoveJsonConfig(projectPath, viteConfig, configs.oxlintConfig, "lint", silent, report);
8173
8492
  }
8174
8493
  if (configs.oxfmtConfig) mergeAndRemoveJsonConfig(projectPath, viteConfig, configs.oxfmtConfig, "fmt", silent, report);
@@ -8315,54 +8634,40 @@ function rewriteAllImports(projectPath, silent = false, report, preserveNuxtVite
8315
8634
  }
8316
8635
  //#endregion
8317
8636
  //#region src/migration/migrator/git-hooks.ts
8318
- /**
8319
- * Check if the project has an unsupported husky version (<9.0.0).
8320
- * Uses `semver.coerce` to handle ranges like `^8.0.0` → `8.0.0`.
8321
- * When the specifier is a catalog reference (e.g. `"catalog:"`), resolves
8322
- * it from the active package manager's catalog first — a `catalog:` spec is
8323
- * only meaningful to the manager that owns the workspace, so we never read a
8324
- * leftover/foreign catalog file. When it is still not coercible (e.g.
8325
- * `"latest"`), falls back to the installed version in node_modules via
8326
- * `detectPackageMetadata`.
8327
- * Returns a reason string if hooks migration should be skipped, or null
8328
- * if husky is absent or compatible.
8329
- */
8330
- function checkUnsupportedHuskyVersion(projectPath, deps, prodDeps, packageManager) {
8331
- const huskyVersion = deps?.husky ?? prodDeps?.husky;
8332
- if (!huskyVersion) return null;
8333
- let coerced = import_semver.default.coerce(huskyVersion);
8334
- if (coerced == null && packageManager != null && huskyVersion.startsWith("catalog:")) {
8335
- const resolved = createCatalogDependencyResolver(projectPath, packageManager)?.(huskyVersion, "husky");
8336
- if (resolved) coerced = import_semver.default.coerce(resolved);
8337
- }
8338
- if (coerced == null) {
8339
- const installed = detectPackageMetadata(projectPath, "husky");
8340
- if (installed) coerced = import_semver.default.coerce(installed.version);
8341
- if (coerced == null) return `Could not determine husky version from "${huskyVersion}" — please specify a semver-compatible version (e.g., "^9.0.0") and re-run migration.`;
8342
- }
8343
- if (import_semver.default.satisfies(coerced, "<9.0.0")) return "Detected husky <9.0.0 — please upgrade to husky v9+ first, then re-run migration.";
8344
- return null;
8345
- }
8637
+ var import_cross_spawn = /* @__PURE__ */ __toESM(require_cross_spawn(), 1);
8346
8638
  const OTHER_HOOK_TOOLS = [
8347
8639
  "simple-git-hooks",
8348
8640
  "lefthook",
8349
8641
  "yorkie"
8350
8642
  ];
8351
- const REPLACED_HOOK_PACKAGES = ["husky", "lint-staged"];
8352
- function removeReplacedHookPackages(packageJsonPath) {
8643
+ const SUPPORTED_GIT_HOOK_NAME_SET = new Set(SUPPORTED_GIT_HOOK_NAMES);
8644
+ function removeReplacedStagedPackage(packageJsonPath) {
8353
8645
  editJsonFile(packageJsonPath, (pkg) => {
8354
- for (const name of REPLACED_HOOK_PACKAGES) {
8355
- if (pkg.devDependencies?.[name]) delete pkg.devDependencies[name];
8356
- if (pkg.dependencies?.[name]) delete pkg.dependencies[name];
8357
- }
8646
+ if (pkg.devDependencies?.["lint-staged"]) delete pkg.devDependencies["lint-staged"];
8647
+ if (pkg.dependencies?.["lint-staged"]) delete pkg.dependencies["lint-staged"];
8358
8648
  return pkg;
8359
8649
  });
8360
8650
  }
8361
8651
  function detectLegacyGitHooksMigrationCandidate(projectPath) {
8362
8652
  const packageJsonPath = path.join(projectPath, "package.json");
8363
8653
  if (!fs.existsSync(packageJsonPath)) return false;
8364
- const pkg = readJsonFile(packageJsonPath);
8365
- return getOldHooksDir(projectPath) !== void 0 || pkg["lint-staged"] !== void 0;
8654
+ return readJsonFile(packageJsonPath)["lint-staged"] !== void 0;
8655
+ }
8656
+ function hasHuskySetup(projectPath, pkg) {
8657
+ return pkg.devDependencies?.husky !== void 0 || pkg.dependencies?.husky !== void 0 || pkg.optionalDependencies?.husky !== void 0 || pkg.peerDependencies?.husky !== void 0 || pkg.husky !== void 0 || (pkg.scripts?.prepare ? /\bhusky\b/.test(pkg.scripts.prepare) : false) || fs.existsSync(path.join(projectPath, ".husky"));
8658
+ }
8659
+ function hasExistingViteHooksPolicy(projectPath) {
8660
+ const hooksDir = path.join(projectPath, ".vite-hooks");
8661
+ if (!fs.lstatSync(hooksDir, { throwIfNoEntry: false })?.isDirectory()) return false;
8662
+ return fs.readdirSync(hooksDir, { withFileTypes: true }).some((entry) => entry.isFile() && SUPPORTED_GIT_HOOK_NAME_SET.has(entry.name));
8663
+ }
8664
+ function findNonRegularViteHookPath(projectPath) {
8665
+ for (const hookName of SUPPORTED_GIT_HOOK_NAMES) {
8666
+ const hookPath = path.join(projectPath, ".vite-hooks", hookName);
8667
+ const stats = fs.lstatSync(hookPath, { throwIfNoEntry: false });
8668
+ if (stats && !stats.isFile()) return path.join(".vite-hooks", hookName);
8669
+ }
8670
+ return null;
8366
8671
  }
8367
8672
  /**
8368
8673
  * Walk up from `startPath` looking for `.git` (directory or file — submodules
@@ -8377,36 +8682,45 @@ function findGitRoot(startPath) {
8377
8682
  dir = parent;
8378
8683
  }
8379
8684
  }
8380
- /**
8381
- * Normalize "husky install [dir]" → "husky [dir]" so downstream regex
8382
- * and ast-grep rules can match a single pattern.
8383
- */
8384
- function collapseHuskyInstall(script) {
8385
- return script.replace("husky install ", "husky ").replace("husky install", "husky");
8386
- }
8387
- /**
8388
- * High-level helper: detect old hooks dir, set up git hooks, and rewrite
8389
- * the prepare script. Returns true if hooks were successfully installed.
8390
- */
8391
- function installGitHooks(projectPath, silent = false, report, packageManager) {
8392
- if (setupGitHooks(projectPath, getOldHooksDir(projectPath), silent, report, packageManager)) {
8393
- rewritePrepareScript(projectPath);
8394
- return true;
8685
+ function findWorkspacePackageHookPolicy(workspaceRoot, packages) {
8686
+ for (const pkg of packages) {
8687
+ const packagePath = path.join(workspaceRoot, pkg.path);
8688
+ if (hasExistingViteHooksPolicy(packagePath)) return `Detected project-owned Vite+ hooks in workspace package "${pkg.path}" — leaving the existing hook setup unchanged.`;
8689
+ const packageJsonPath = path.join(packagePath, "package.json");
8690
+ const pkgContent = fs.existsSync(packageJsonPath) ? readJsonFile(packageJsonPath) : {};
8691
+ if (hasHuskySetup(packagePath, pkgContent)) return `Detected Husky in workspace package "${pkg.path}" — leaving its hooks, configuration, and dependencies unchanged.`;
8692
+ const deps = pkgContent.devDependencies;
8693
+ const prodDeps = pkgContent.dependencies;
8694
+ for (const tool of OTHER_HOOK_TOOLS) if (deps?.[tool] || prodDeps?.[tool] || pkgContent[tool]) return `Detected ${tool} in workspace package "${pkg.path}" — leaving the existing hook setup unchanged.`;
8695
+ const gitRoot = findGitRoot(packagePath);
8696
+ if (gitRoot && path.resolve(packagePath) === path.resolve(gitRoot)) {
8697
+ const configuredHooksPath = getConfiguredHooksPath(packagePath);
8698
+ const normalizedHooksPath = normalizeGitHooksPath(configuredHooksPath);
8699
+ if (configuredHooksPath && normalizedHooksPath !== ".vite-hooks/_") return `core.hooksPath is already set to "${configuredHooksPath}" in workspace package "${pkg.path}" — leaving the existing hook setup unchanged.`;
8700
+ }
8395
8701
  }
8396
- return false;
8702
+ return null;
8703
+ }
8704
+ function getConfiguredHooksPath(projectPath) {
8705
+ const result = import_cross_spawn.default.sync("git", [
8706
+ "config",
8707
+ "--get",
8708
+ "core.hooksPath"
8709
+ ], {
8710
+ cwd: projectPath,
8711
+ stdio: "pipe"
8712
+ });
8713
+ return result.status === 0 ? result.stdout?.toString().trim() ?? "" : "";
8714
+ }
8715
+ function normalizeGitHooksPath(hooksPath) {
8716
+ return path.posix.normalize(hooksPath.replaceAll("\\", "/")).replace(/\/$/, "");
8397
8717
  }
8398
8718
  /**
8399
- * Read-only probe: extract the old husky hooks directory from `scripts.prepare`
8400
- * without modifying package.json. Returns undefined when no husky reference is found.
8719
+ * High-level helper that sets up Vite+ hooks when no other hook tool owns the
8720
+ * project. Husky setups are deliberately preserved for a dedicated migration.
8401
8721
  */
8402
- function getOldHooksDir(rootDir) {
8403
- const packageJsonPath = path.join(rootDir, "package.json");
8404
- if (!fs.existsSync(packageJsonPath)) return;
8405
- const pkg = readJsonFile(packageJsonPath);
8406
- if (!pkg.scripts?.prepare) return;
8407
- const match = collapseHuskyInstall(pkg.scripts.prepare).match(/\bhusky(?:\s+([\w./-]+))?/);
8408
- if (!match) return;
8409
- return match[1] ?? ".husky";
8722
+ function installGitHooks(projectPath, silent = false, report, packageManager, packages = []) {
8723
+ return setupGitHooks(projectPath, silent, report, packageManager, packages);
8410
8724
  }
8411
8725
  /**
8412
8726
  * Pre-flight check: verify that git hooks can be set up for this project.
@@ -8416,10 +8730,9 @@ function getOldHooksDir(rootDir) {
8416
8730
  * These checks are deterministic and read-only — they do not modify
8417
8731
  * the project in any way, making them safe to call before migration.
8418
8732
  *
8419
- * `packageManager` is the project's detected manager; it scopes `catalog:`
8420
- * resolution to that manager's catalog so a foreign catalog file is ignored.
8733
+ * The package manager argument remains for API compatibility with callers.
8421
8734
  */
8422
- function preflightGitHooksSetup(projectPath, packageManager) {
8735
+ function preflightGitHooksSetup(projectPath, _packageManager, packages = []) {
8423
8736
  const gitRoot = findGitRoot(projectPath);
8424
8737
  if (gitRoot && path.resolve(projectPath) !== path.resolve(gitRoot)) return "Subdirectory project detected — skipping git hooks setup. Configure hooks at the repository root.";
8425
8738
  const packageJsonPath = path.join(projectPath, "package.json");
@@ -8427,19 +8740,41 @@ function preflightGitHooksSetup(projectPath, packageManager) {
8427
8740
  const pkgContent = readJsonFile(packageJsonPath);
8428
8741
  const deps = pkgContent.devDependencies;
8429
8742
  const prodDeps = pkgContent.dependencies;
8743
+ const configuredHooksPath = gitRoot ? getConfiguredHooksPath(projectPath) : "";
8744
+ const normalizedHooksPath = normalizeGitHooksPath(configuredHooksPath);
8745
+ if (hasHuskySetup(projectPath, pkgContent) || normalizedHooksPath === ".husky" || normalizedHooksPath.startsWith(".husky/")) return "Detected Husky — leaving its hooks, configuration, and dependencies unchanged. Migrate Husky manually before enabling Vite+ hooks.";
8430
8746
  for (const tool of OTHER_HOOK_TOOLS) if (deps?.[tool] || prodDeps?.[tool] || pkgContent[tool]) return `Detected ${tool} — skipping git hooks setup. Please configure git hooks manually, see https://viteplus.dev/guide/migrate#git-hook-tools`;
8431
- const huskyReason = checkUnsupportedHuskyVersion(projectPath, deps, prodDeps, packageManager);
8432
- if (huskyReason) return huskyReason;
8747
+ const workspacePackageReason = findWorkspacePackageHookPolicy(projectPath, packages);
8748
+ if (workspacePackageReason) return workspacePackageReason;
8749
+ const disabledHooksEnvironment = [
8750
+ "HUSKY",
8751
+ "VP_GIT_HOOKS",
8752
+ "VITE_GIT_HOOKS"
8753
+ ].find((name) => process.env[name] === "0");
8754
+ if (disabledHooksEnvironment) return `Git hooks are disabled through ${disabledHooksEnvironment}=0 — skipping git hooks setup.`;
8755
+ if (configuredHooksPath && normalizedHooksPath !== ".vite-hooks/_") return `core.hooksPath is already set to "${configuredHooksPath}" — leaving the existing hook setup unchanged.`;
8756
+ const unsafeInstallPath = findUnsafeHookInstallPath(projectPath, ".vite-hooks");
8757
+ if (unsafeInstallPath) return `Git hook dispatcher path "${unsafeInstallPath.relativePath}" is unsafe — leaving the existing hook setup unchanged.`;
8758
+ const nonRegularHookPath = findNonRegularViteHookPath(projectPath);
8759
+ if (nonRegularHookPath) return `Git hook path "${nonRegularHookPath}" is not a regular file — leaving the existing hook setup unchanged.`;
8433
8760
  if (hasUnsupportedLintStagedConfig(projectPath)) return "Unsupported lint-staged config format — skipping git hooks setup. Please configure git hooks manually.";
8434
8761
  return null;
8435
8762
  }
8436
8763
  /**
8764
+ * Decide whether config rewriting must leave lint-staged configuration in
8765
+ * place. Call this after scaffolding but before rewriting the project so an
8766
+ * existing hook owner never observes a partially migrated configuration.
8767
+ */
8768
+ function shouldSkipStagedMigrationForHooks(projectPath, shouldSetupHooks, packageManager, packages = []) {
8769
+ return !shouldSetupHooks || hasExistingViteHooksPolicy(projectPath) || preflightGitHooksSetup(projectPath, packageManager, packages) !== null;
8770
+ }
8771
+ /**
8437
8772
  * Set up git hooks with husky + lint-staged via vp commands.
8438
8773
  * Skips if another hook tool is detected (warns user).
8439
8774
  * Returns true if hooks were successfully set up, false if skipped.
8440
8775
  */
8441
- function setupGitHooks(projectPath, oldHooksDir, silent = false, report, packageManager) {
8442
- const reason = preflightGitHooksSetup(projectPath, packageManager);
8776
+ function setupGitHooks(projectPath, silent = false, report, packageManager, packages = []) {
8777
+ const reason = preflightGitHooksSetup(projectPath, packageManager, packages);
8443
8778
  if (reason) {
8444
8779
  warnMigration(reason, report);
8445
8780
  return false;
@@ -8447,73 +8782,29 @@ function setupGitHooks(projectPath, oldHooksDir, silent = false, report, package
8447
8782
  const packageJsonPath = path.join(projectPath, "package.json");
8448
8783
  if (!fs.existsSync(packageJsonPath)) return false;
8449
8784
  const gitRoot = findGitRoot(projectPath);
8450
- const isCustomDir = oldHooksDir != null && oldHooksDir !== ".husky";
8451
- const hooksDir = isCustomDir ? oldHooksDir : ".vite-hooks";
8785
+ const hooksDir = ".vite-hooks";
8786
+ const hasExistingHookPolicy = hasExistingViteHooksPolicy(projectPath);
8452
8787
  editJsonFile(packageJsonPath, (pkg) => {
8453
8788
  if (!pkg.scripts) pkg.scripts = {};
8454
8789
  if (!pkg.scripts.prepare) pkg.scripts.prepare = "vp config";
8455
- else if (!pkg.scripts.prepare.includes("vp config") && !/\bhusky\b/.test(pkg.scripts.prepare)) pkg.scripts.prepare = `vp config && ${pkg.scripts.prepare}`;
8790
+ else if (!pkg.scripts.prepare.includes("vp config")) pkg.scripts.prepare = `vp config && ${pkg.scripts.prepare}`;
8456
8791
  return pkg;
8457
8792
  });
8458
8793
  let stagedMerged = hasStagedConfigInViteConfig(projectPath);
8459
8794
  const hasStandaloneConfig = hasStandaloneLintStagedConfig(projectPath);
8460
- if (!stagedMerged && !hasStandaloneConfig) {
8795
+ if (!hasExistingHookPolicy && !stagedMerged && !hasStandaloneConfig) {
8461
8796
  const stagedConfig = readJsonFile(packageJsonPath)?.["lint-staged"] ?? DEFAULT_STAGED_CONFIG;
8462
8797
  const updated = rewriteScripts(JSON.stringify(stagedConfig), readRulesYaml());
8463
8798
  stagedMerged = mergeStagedConfigToViteConfig(projectPath, updated ? JSON.parse(updated) : stagedConfig, silent, report);
8464
8799
  }
8465
- if (stagedMerged) removeLintStagedFromPackageJson(packageJsonPath);
8466
- if (oldHooksDir && !isCustomDir) {
8467
- const oldDir = path.join(projectPath, oldHooksDir);
8468
- if (fs.existsSync(oldDir)) {
8469
- const targetDir = path.join(projectPath, hooksDir);
8470
- fs.mkdirSync(targetDir, { recursive: true });
8471
- for (const entry of fs.readdirSync(oldDir, { withFileTypes: true })) {
8472
- if (entry.isDirectory() || entry.name.startsWith(".")) continue;
8473
- const src = path.join(oldDir, entry.name);
8474
- const dest = path.join(targetDir, entry.name);
8475
- fs.copyFileSync(src, dest);
8476
- fs.chmodSync(dest, 493);
8477
- }
8478
- fs.rmSync(oldDir, {
8479
- recursive: true,
8480
- force: true
8481
- });
8482
- }
8483
- }
8484
- if (stagedMerged) createPreCommitHook(projectPath, hooksDir);
8800
+ if (!hasExistingHookPolicy && stagedMerged) removeLintStagedFromPackageJson(packageJsonPath);
8801
+ if (!hasExistingHookPolicy && stagedMerged) createPreCommitHook(projectPath, hooksDir);
8485
8802
  if (!gitRoot) {
8486
- removeReplacedHookPackages(packageJsonPath);
8803
+ if (!hasExistingHookPolicy && stagedMerged) removeReplacedStagedPackage(packageJsonPath);
8487
8804
  return true;
8488
8805
  }
8489
- if (oldHooksDir) {
8490
- const checkResult = import_cross_spawn.default.sync("git", [
8491
- "config",
8492
- "--local",
8493
- "core.hooksPath"
8494
- ], {
8495
- cwd: projectPath,
8496
- stdio: "pipe"
8497
- });
8498
- const existingPath = checkResult.status === 0 ? checkResult.stdout?.toString().trim() : "";
8499
- if (existingPath === `${oldHooksDir}/_` || existingPath === oldHooksDir) import_cross_spawn.default.sync("git", [
8500
- "config",
8501
- "--local",
8502
- "--unset",
8503
- "core.hooksPath"
8504
- ], {
8505
- cwd: projectPath,
8506
- stdio: "pipe"
8507
- });
8508
- }
8509
8806
  const vpBin = process.env.VP_CLI_BIN ?? "vp";
8510
- const configArgs = isCustomDir ? [
8511
- "config",
8512
- "--no-agent",
8513
- "--hooks-dir",
8514
- hooksDir
8515
- ] : ["config", "--no-agent"];
8516
- const configResult = import_cross_spawn.default.sync(vpBin, configArgs, {
8807
+ const configResult = import_cross_spawn.default.sync(vpBin, ["config", "--no-agent"], {
8517
8808
  cwd: projectPath,
8518
8809
  stdio: "pipe"
8519
8810
  });
@@ -8523,7 +8814,7 @@ function setupGitHooks(projectPath, oldHooksDir, silent = false, report, package
8523
8814
  warnMigration(`Git hooks not configured — ${stdout}`, report);
8524
8815
  return false;
8525
8816
  }
8526
- removeReplacedHookPackages(packageJsonPath);
8817
+ if (!hasExistingHookPolicy && stagedMerged) removeReplacedStagedPackage(packageJsonPath);
8527
8818
  if (report) report.gitHooksConfigured = true;
8528
8819
  if (!silent) log.success("✔ Git hooks configured");
8529
8820
  return true;
@@ -8548,85 +8839,14 @@ function hasUnsupportedLintStagedConfig(projectPath) {
8548
8839
  if (fs.existsSync(lintstagedrcPath) && !isJsonFile(lintstagedrcPath)) return true;
8549
8840
  return false;
8550
8841
  }
8551
- /**
8552
- * Create pre-commit hook file in the hooks directory.
8553
- */
8554
- const STALE_LINT_STAGED_PATTERNS = [/^((?:[A-Z_][A-Z0-9_]*(?:=\S*)?\s+)*)(pnpm|pnpm exec|npx|yarn|yarn run|npm exec|npm run|bunx|bun run|bun x)\s+lint-staged\b/, /^((?:[A-Z_][A-Z0-9_]*(?:=\S*)?\s+)*)lint-staged\b/];
8555
8842
  const DEFAULT_STAGED_CONFIG = { "*": "vp check --fix" };
8556
- /**
8557
- * Ensure the pre-commit hook exists with `vp staged`, and that
8558
- * vite.config.ts contains a `staged` block (using the default config
8559
- * if none is present). Called by `vp config` after hook installation.
8560
- */
8561
- function ensurePreCommitHook(projectPath, dir = ".vite-hooks") {
8562
- if (!hasStagedConfigInViteConfig(projectPath)) mergeStagedConfigToViteConfig(projectPath, DEFAULT_STAGED_CONFIG, true);
8563
- createPreCommitHook(projectPath, dir);
8564
- }
8565
8843
  function createPreCommitHook(projectPath, dir = ".vite-hooks") {
8566
8844
  const huskyDir = path.join(projectPath, dir);
8567
8845
  fs.mkdirSync(huskyDir, { recursive: true });
8568
8846
  const hookPath = path.join(huskyDir, "pre-commit");
8569
- if (fs.existsSync(hookPath)) {
8570
- const existing = fs.readFileSync(hookPath, "utf8");
8571
- if (existing.includes("vp staged")) return;
8572
- const lines = existing.split("\n");
8573
- let replaced = false;
8574
- const result = [];
8575
- for (const line of lines) {
8576
- const trimmed = line.trim();
8577
- if (!replaced) {
8578
- let matched = false;
8579
- for (const pattern of STALE_LINT_STAGED_PATTERNS) {
8580
- const match = pattern.exec(trimmed);
8581
- if (match) {
8582
- const parts = [
8583
- match[1]?.trim() ?? "",
8584
- "vp staged",
8585
- trimmed.slice(match[0].length).trim()
8586
- ].filter(Boolean);
8587
- result.push(parts.join(" "));
8588
- replaced = true;
8589
- matched = true;
8590
- break;
8591
- }
8592
- }
8593
- if (matched) continue;
8594
- }
8595
- result.push(line);
8596
- }
8597
- if (!replaced) fs.writeFileSync(hookPath, `${result.join("\n").trimEnd()}\nvp staged\n`);
8598
- else fs.writeFileSync(hookPath, result.join("\n"));
8599
- } else {
8600
- fs.writeFileSync(hookPath, "vp staged\n");
8601
- fs.chmodSync(hookPath, 493);
8602
- }
8603
- }
8604
- /**
8605
- * Rewrite only `scripts.prepare` in the root package.json using vite-prepare.yml rules.
8606
- * Collapses "husky install" → "husky" before applying ast-grep so that the
8607
- * replace-husky rule produces "vp config" with any directory argument preserved.
8608
- * Returns the old husky hooks dir (if any) for migration to .vite-hooks.
8609
- * Called only when hooks are being set up (not with --no-hooks).
8610
- */
8611
- function rewritePrepareScript(rootDir) {
8612
- const packageJsonPath = path.join(rootDir, "package.json");
8613
- if (!fs.existsSync(packageJsonPath)) return;
8614
- let oldDir;
8615
- editJsonFile(packageJsonPath, (pkg) => {
8616
- if (!pkg.scripts?.prepare) return pkg;
8617
- const prepare = collapseHuskyInstall(pkg.scripts.prepare);
8618
- const updated = rewriteScripts(JSON.stringify({ prepare }), readPrepareRulesYaml());
8619
- if (updated) {
8620
- let newPrepare = JSON.parse(updated).prepare;
8621
- newPrepare = newPrepare.replace(/\bvp config(?:\s+(?!-)([\w./-]+))?/, (_match, dir) => {
8622
- oldDir = dir ?? ".husky";
8623
- return dir ? `vp config --hooks-dir ${dir}` : "vp config";
8624
- });
8625
- pkg.scripts.prepare = newPrepare;
8626
- } else if (prepare !== pkg.scripts.prepare) pkg.scripts.prepare = prepare;
8627
- return pkg;
8628
- });
8629
- return oldDir;
8847
+ if (fs.existsSync(hookPath)) return;
8848
+ fs.writeFileSync(hookPath, "vp staged\n");
8849
+ fs.chmodSync(hookPath, 493);
8630
8850
  }
8631
8851
  //#endregion
8632
8852
  //#region src/migration/migrator/setup.ts
@@ -8769,10 +8989,9 @@ function migrateNodeVersionManagerFile(projectPath, detection, report) {
8769
8989
  //#endregion
8770
8990
  //#region src/migration/migrator/core-finalization.ts
8771
8991
  const RULES_YAML_PATH = path.join(rulesDir, "vite-tools.yml");
8772
- const PREPARE_RULES_YAML_PATH = path.join(rulesDir, "vite-prepare.yml");
8992
+ path.join(rulesDir, "vite-prepare.yml");
8773
8993
  let cachedRulesYaml;
8774
8994
  let cachedRulesYamlNoLintStaged;
8775
- let cachedPrepareRulesYaml;
8776
8995
  function readRulesYaml() {
8777
8996
  cachedRulesYaml ??= fs.readFileSync(RULES_YAML_PATH, "utf8");
8778
8997
  return cachedRulesYaml;
@@ -8783,10 +9002,6 @@ function getScriptRulesYaml(skipStagedMigration) {
8783
9002
  cachedRulesYamlNoLintStaged ??= yaml.split("\n\n\n").filter((block) => !block.includes("id: replace-lint-staged")).join("\n\n\n");
8784
9003
  return cachedRulesYamlNoLintStaged;
8785
9004
  }
8786
- function readPrepareRulesYaml() {
8787
- cachedPrepareRulesYaml ??= fs.readFileSync(PREPARE_RULES_YAML_PATH, "utf8");
8788
- return cachedPrepareRulesYaml;
8789
- }
8790
9005
  function getCoreMigrationProjectPaths(workspaceInfo) {
8791
9006
  return [workspaceInfo.rootDir, ...(workspaceInfo.packages ?? []).map((pkg) => path.join(workspaceInfo.rootDir, pkg.path))];
8792
9007
  }
@@ -9014,300 +9229,186 @@ function rewriteMonorepoProject(projectPath, packageManager, skipStagedMigration
9014
9229
  if (!deferLazyPluginWrapping) wrapLazyPluginsInViteConfig(projectPath, silent, report);
9015
9230
  }
9016
9231
  //#endregion
9017
- //#region src/utils/agent.ts
9018
- const AGENTS = [
9019
- {
9020
- id: "agents",
9021
- label: "AGENTS.md",
9022
- targetPath: "AGENTS.md",
9023
- hint: "Codex, Amp, OpenCode, and similar agents",
9024
- aliases: [
9025
- "agents.md",
9026
- "chatgpt",
9027
- "chatgpt-codex",
9028
- "codex",
9029
- "amp",
9030
- "kilo",
9031
- "kilo-code",
9032
- "kiro",
9033
- "kiro-cli",
9034
- "opencode",
9035
- "other"
9036
- ]
9037
- },
9038
- {
9039
- id: "claude",
9040
- label: "CLAUDE.md",
9041
- targetPath: "CLAUDE.md",
9042
- hint: "Claude Code",
9043
- aliases: ["claude.md", "claude-code"]
9044
- },
9045
- {
9046
- id: "gemini",
9047
- label: "GEMINI.md",
9048
- targetPath: "GEMINI.md",
9049
- hint: "Gemini CLI",
9050
- aliases: ["gemini.md", "gemini-cli"]
9051
- },
9052
- {
9053
- id: "copilot",
9054
- label: ".github/copilot-instructions.md",
9055
- targetPath: ".github/copilot-instructions.md",
9056
- hint: "GitHub Copilot",
9057
- aliases: ["github-copilot", "copilot-instructions.md"]
9058
- },
9059
- {
9060
- id: "cursor",
9061
- label: ".cursor/rules/viteplus.mdc",
9062
- targetPath: ".cursor/rules/viteplus.mdc",
9063
- hint: "Cursor",
9064
- aliases: ["viteplus.mdc"]
9232
+ //#region src/utils/editor.ts
9233
+ const VSCODE_SETTINGS = {
9234
+ "editor.defaultFormatter": "oxc.oxc-vscode",
9235
+ "[javascript]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
9236
+ "[javascriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
9237
+ "[typescript]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
9238
+ "[typescriptreact]": { "editor.defaultFormatter": "oxc.oxc-vscode" },
9239
+ "oxc.fmt.configPath": "./vite.config.ts",
9240
+ "editor.formatOnSave": true,
9241
+ "editor.formatOnSaveMode": "file",
9242
+ "editor.codeActionsOnSave": { "source.fixAll.oxc": "explicit" }
9243
+ };
9244
+ const VSCODE_EXTENSIONS = { recommendations: ["VoidZero.vite-plus-extension-pack"] };
9245
+ const ZED_SETTINGS = {
9246
+ lsp: {
9247
+ oxlint: { initialization_options: { settings: {
9248
+ run: "onType",
9249
+ fixKind: "safe_fix",
9250
+ typeAware: true,
9251
+ unusedDisableDirectives: "deny"
9252
+ } } },
9253
+ oxfmt: { initialization_options: { settings: {
9254
+ "fmt.configPath": "./vite.config.ts",
9255
+ run: "onSave"
9256
+ } } }
9065
9257
  },
9066
- {
9067
- id: "jetbrains",
9068
- label: ".aiassistant/rules/viteplus.md",
9069
- targetPath: ".aiassistant/rules/viteplus.md",
9070
- hint: "JetBrains AI Assistant",
9071
- aliases: [
9072
- "jetbrains",
9073
- "jetbrains-ai-assistant",
9074
- "aiassistant",
9075
- "viteplus.md"
9076
- ]
9258
+ languages: Object.fromEntries([
9259
+ "CSS",
9260
+ "GraphQL",
9261
+ "Handlebars",
9262
+ "HTML",
9263
+ "JavaScript",
9264
+ "JSX",
9265
+ "JSON",
9266
+ "JSON5",
9267
+ "JSONC",
9268
+ "Less",
9269
+ "Markdown",
9270
+ "MDX",
9271
+ "SCSS",
9272
+ "TypeScript",
9273
+ "TSX",
9274
+ "Vue.js",
9275
+ "YAML"
9276
+ ].map((language) => [language, {
9277
+ format_on_save: "on",
9278
+ prettier: { allowed: false },
9279
+ formatter: [{ language_server: { name: "oxfmt" } }],
9280
+ ...language === "JavaScript" ? { code_action: "source.fixAll.oxc" } : {}
9281
+ }]))
9282
+ };
9283
+ const EDITORS = [{
9284
+ id: "vscode",
9285
+ label: "VSCode",
9286
+ targetDir: ".vscode",
9287
+ files: {
9288
+ "settings.json": VSCODE_SETTINGS,
9289
+ "extensions.json": VSCODE_EXTENSIONS
9290
+ }
9291
+ }, {
9292
+ id: "zed",
9293
+ label: "Zed",
9294
+ targetDir: ".zed",
9295
+ files: { "settings.json": ZED_SETTINGS }
9296
+ }];
9297
+ async function selectEditor({ interactive, editor, onCancel }) {
9298
+ if (editor === false) return;
9299
+ if (interactive && !editor) {
9300
+ const editorOptions = EDITORS.map((option) => ({
9301
+ label: option.label,
9302
+ value: option.id,
9303
+ hint: option.targetDir
9304
+ }));
9305
+ const otherOption = {
9306
+ label: "Other",
9307
+ value: null,
9308
+ hint: "Skip writing editor configs"
9309
+ };
9310
+ const selectedEditor = await select({
9311
+ message: "Which editor are you using?\n " + styleText("gray", "Writes editor config files to enable recommended extensions and Oxlint/Oxfmt integrations."),
9312
+ options: [...editorOptions, otherOption],
9313
+ initialValue: "vscode"
9314
+ });
9315
+ if (isCancel(selectedEditor)) {
9316
+ onCancel();
9317
+ return;
9318
+ }
9319
+ if (selectedEditor === null) return;
9320
+ return resolveEditorId(selectedEditor);
9077
9321
  }
9078
- ];
9079
- const AGENT_DEFAULT_ID = "agents";
9080
- const AGENT_STANDARD_PATH = "AGENTS.md";
9081
- const COPILOT_AGENT_ID = "copilot";
9082
- const COPILOT_SETUP_WORKFLOW_PATH = ".github/workflows/copilot-setup-steps.yml";
9083
- const AGENT_INSTRUCTIONS_START_MARKER = "<!--VITE PLUS START-->";
9084
- const AGENT_INSTRUCTIONS_END_MARKER = "<!--VITE PLUS END-->";
9085
- const AGENT_ALIASES = Object.fromEntries(AGENTS.flatMap((option) => (option.aliases ?? []).map((alias) => [normalizeAgentName(alias), option.id])));
9086
- const COPILOT_SETUP_WORKFLOW_CONTENT = `name: "Copilot Setup Steps"
9087
-
9088
- on:
9089
- workflow_dispatch:
9090
- push:
9091
- paths:
9092
- - .github/workflows/copilot-setup-steps.yml
9093
- pull_request:
9094
- paths:
9095
- - .github/workflows/copilot-setup-steps.yml
9096
-
9097
- jobs:
9098
- copilot-setup-steps:
9099
- runs-on: ubuntu-latest
9100
- permissions:
9101
- contents: read
9102
- steps:
9103
- - name: Checkout code
9104
- uses: actions/checkout@v6
9105
- with:
9106
- persist-credentials: false
9107
- - name: Set up Vite+
9108
- uses: voidzero-dev/setup-vp@v1
9109
- with:
9110
- cache: true
9111
- run-install: true
9112
- - name: Verify Vite+
9113
- run: vp --version
9114
- `;
9115
- async function selectAgentTargets({ interactive, agent, onCancel }) {
9116
- if (agent === false) return {
9117
- targetPaths: void 0,
9118
- selectedAgents: []
9119
- };
9120
- if (interactive && !agent) {
9121
- const selectedAgentIds = await multiselect({
9122
- message: "Which coding agent instruction files should Vite+ create?",
9123
- options: AGENTS.map((option) => ({
9322
+ if (editor) return resolveEditorId(editor);
9323
+ }
9324
+ async function selectEditors({ interactive, editor, onCancel }) {
9325
+ if (editor === false) return;
9326
+ if (interactive && !editor) {
9327
+ const selectedEditors = await multiselect({
9328
+ message: "Which editors are you using?\n " + styleText("gray", "Writes editor config files to enable recommended extensions and Oxlint/Oxfmt integrations."),
9329
+ options: EDITORS.map((option) => ({
9124
9330
  label: option.label,
9125
9331
  value: option.id,
9126
- hint: option.hint
9332
+ hint: option.targetDir
9127
9333
  })),
9128
- initialValues: [AGENT_DEFAULT_ID],
9334
+ initialValues: ["vscode"],
9129
9335
  required: false
9130
9336
  });
9131
- if (isCancel(selectedAgentIds)) {
9337
+ if (isCancel(selectedEditors)) {
9132
9338
  onCancel();
9133
- return {
9134
- targetPaths: void 0,
9135
- selectedAgents: []
9136
- };
9339
+ return;
9137
9340
  }
9138
- if (selectedAgentIds.length === 0) return {
9139
- targetPaths: void 0,
9140
- selectedAgents: []
9141
- };
9142
- const selectedAgents = resolveAgentOptions(selectedAgentIds);
9143
- return {
9144
- targetPaths: getAgentTargetPaths(selectedAgents),
9145
- selectedAgents
9146
- };
9341
+ return selectedEditors.length === 0 ? void 0 : resolveEditorIds(selectedEditors);
9147
9342
  }
9148
- const selectedAgents = resolveAgentOptions(agent ?? AGENT_DEFAULT_ID);
9149
- return {
9150
- targetPaths: getAgentTargetPaths(selectedAgents),
9151
- selectedAgents
9152
- };
9153
- }
9154
- async function selectAgentTargetPaths({ interactive, agent, onCancel }) {
9155
- return (await selectAgentTargets({
9156
- interactive,
9157
- agent,
9158
- onCancel
9159
- })).targetPaths;
9160
- }
9161
- function detectExistingAgentTargetPaths(projectRoot) {
9162
- const detectedPaths = [];
9163
- const seenTargetPaths = /* @__PURE__ */ new Set();
9164
- for (const option of AGENTS) {
9165
- if (seenTargetPaths.has(option.targetPath)) continue;
9166
- seenTargetPaths.add(option.targetPath);
9167
- const targetPath = path.join(projectRoot, option.targetPath);
9168
- if (fs.existsSync(targetPath) && !fs.lstatSync(targetPath).isSymbolicLink()) detectedPaths.push(option.targetPath);
9343
+ if (editor) {
9344
+ const editorId = resolveEditorId(editor);
9345
+ return editorId ? [editorId] : void 0;
9169
9346
  }
9170
- return detectedPaths.length > 0 ? detectedPaths : void 0;
9171
- }
9172
- /**
9173
- * Silently update agent instruction files that contain Vite+ markers.
9174
- * - No agent files → no writes
9175
- * - No Vite+ markers → no writes
9176
- * - Markers present, content up to date → no writes
9177
- * - Markers present, content outdated → update marked section
9178
- */
9179
- function updateExistingAgentInstructions(projectRoot) {
9180
- const targetPaths = detectExistingAgentTargetPaths(projectRoot);
9181
- if (!targetPaths) return;
9182
- const templatePath = path.join(pkgRoot, "AGENTS.md");
9183
- if (!fs.existsSync(templatePath)) return;
9184
- const templateContent = fs.readFileSync(templatePath, "utf-8");
9185
- for (const targetPath of targetPaths) try {
9186
- const fullPath = path.join(projectRoot, targetPath);
9187
- const existing = fs.readFileSync(fullPath, "utf-8");
9188
- const updated = replaceMarkedAgentInstructionsSection(existing, templateContent);
9189
- if (updated !== void 0 && updated !== existing) fs.writeFileSync(fullPath, updated);
9190
- } catch {}
9191
9347
  }
9192
- function resolveAgentOptions(agent) {
9193
- const agentNames = parseAgentNames(agent);
9194
- const resolvedAgentNames = agentNames.length > 0 ? agentNames : [AGENT_DEFAULT_ID];
9195
- const dedupedAgents = [];
9196
- const seenAgentIds = /* @__PURE__ */ new Set();
9197
- for (const name of resolvedAgentNames) {
9198
- const option = resolveSingleAgentOption(name);
9199
- if (seenAgentIds.has(option.id)) continue;
9200
- seenAgentIds.add(option.id);
9201
- dedupedAgents.push(option);
9202
- }
9203
- return dedupedAgents;
9204
- }
9205
- function getAgentTargetPaths(agents) {
9206
- const dedupedTargetPaths = [];
9207
- const seenTargetPaths = /* @__PURE__ */ new Set();
9208
- for (const agent of agents) {
9209
- if (seenTargetPaths.has(agent.targetPath)) continue;
9210
- seenTargetPaths.add(agent.targetPath);
9211
- dedupedTargetPaths.push(agent.targetPath);
9212
- }
9213
- return dedupedTargetPaths;
9214
- }
9215
- function parseAgentNames(agent) {
9216
- if (!agent) return [];
9217
- return (Array.isArray(agent) ? agent : [agent]).filter((value) => typeof value === "string").flatMap((value) => value.split(",")).map((value) => value.trim()).filter((value) => value.length > 0);
9218
- }
9219
- function resolveSingleAgentOption(agent) {
9220
- const normalized = normalizeAgentName(agent);
9221
- const alias = AGENT_ALIASES[normalized];
9222
- const resolved = alias ? normalizeAgentName(alias) : normalized;
9223
- return AGENTS.find((option) => normalizeAgentName(option.id) === resolved || normalizeAgentName(option.label) === resolved || normalizeAgentName(option.targetPath) === resolved || option.aliases?.some((candidate) => normalizeAgentName(candidate) === resolved)) ?? AGENTS.find((option) => option.id === AGENT_DEFAULT_ID);
9348
+ function detectExistingEditors(projectRoot) {
9349
+ const editors = [];
9350
+ for (const option of EDITORS) for (const fileName of Object.keys(option.files)) {
9351
+ const filePath = path.join(projectRoot, option.targetDir, fileName);
9352
+ if (fs.existsSync(filePath)) {
9353
+ editors.push(option.id);
9354
+ break;
9355
+ }
9356
+ }
9357
+ return editors.length === 0 ? void 0 : editors;
9224
9358
  }
9225
9359
  /**
9226
- * Detect agent instruction files that would conflict (exist without markers).
9227
- * Returns only files that need a user decision (append or skip).
9360
+ * Detect editor config files that would conflict (already exist).
9228
9361
  * Read-only — does not write or modify any files.
9229
9362
  */
9230
- async function detectAgentConflicts({ projectRoot, targetPaths }) {
9231
- if (!targetPaths || targetPaths.length === 0) return [];
9232
- const sourcePath = path.join(pkgRoot, "AGENTS.md");
9233
- if (!fs.existsSync(sourcePath)) return [];
9234
- const incomingContent = await fsPromises.readFile(sourcePath, "utf-8");
9235
- const shouldLinkToAgents = targetPaths.includes(AGENT_STANDARD_PATH);
9236
- const orderedPaths = shouldLinkToAgents ? [AGENT_STANDARD_PATH, ...targetPaths.filter((p) => p !== AGENT_STANDARD_PATH)] : targetPaths;
9363
+ function detectEditorConflicts({ projectRoot, editorId }) {
9364
+ if (!editorId) return [];
9365
+ const editorConfig = EDITORS.find((e) => e.id === editorId);
9366
+ if (!editorConfig) return [];
9237
9367
  const conflicts = [];
9238
- const seenDestinationPaths = /* @__PURE__ */ new Set();
9239
- const seenRealPaths = /* @__PURE__ */ new Set();
9240
- for (const targetPathToCheck of orderedPaths) {
9241
- const destinationPath = path.join(projectRoot, targetPathToCheck);
9242
- const destinationKey = path.resolve(destinationPath);
9243
- if (seenDestinationPaths.has(destinationKey)) continue;
9244
- seenDestinationPaths.add(destinationKey);
9245
- if (shouldLinkToAgents && targetPathToCheck !== AGENT_STANDARD_PATH) {
9246
- if (await getExistingPathKind(destinationPath) !== "file") continue;
9247
- }
9248
- if (fs.existsSync(destinationPath)) {
9249
- if (fs.lstatSync(destinationPath).isSymbolicLink()) continue;
9250
- const destinationRealPath = await fsPromises.realpath(destinationPath);
9251
- if (seenRealPaths.has(destinationRealPath)) continue;
9252
- if (replaceMarkedAgentInstructionsSection(await fsPromises.readFile(destinationPath, "utf-8"), incomingContent) !== void 0) {
9253
- seenRealPaths.add(destinationRealPath);
9254
- continue;
9255
- }
9256
- conflicts.push({ targetPath: targetPathToCheck });
9257
- seenRealPaths.add(destinationRealPath);
9258
- }
9368
+ for (const fileName of Object.keys(editorConfig.files)) {
9369
+ const filePath = path.join(projectRoot, editorConfig.targetDir, fileName);
9370
+ if (fs.existsSync(filePath)) conflicts.push({
9371
+ fileName,
9372
+ displayPath: `${editorConfig.targetDir}/${fileName}`
9373
+ });
9259
9374
  }
9260
9375
  return conflicts;
9261
9376
  }
9262
- async function writeAgentInstructions({ projectRoot, targetPath, targetPaths, interactive, conflictDecisions, silent = false }) {
9263
- const paths = [...targetPaths ?? [], ...targetPath ? [targetPath] : []];
9264
- if (paths.length === 0) return;
9265
- const sourcePath = path.join(pkgRoot, "AGENTS.md");
9266
- if (!fs.existsSync(sourcePath)) {
9267
- if (!silent) log.warn("Agent instructions template not found; skipping.");
9268
- return;
9269
- }
9270
- const seenDestinationPaths = /* @__PURE__ */ new Set();
9271
- const seenRealPaths = /* @__PURE__ */ new Set();
9272
- const incomingContent = await fsPromises.readFile(sourcePath, "utf-8");
9273
- const shouldLinkToAgents = paths.includes(AGENT_STANDARD_PATH);
9274
- const orderedPaths = shouldLinkToAgents ? [AGENT_STANDARD_PATH, ...paths.filter((p) => p !== AGENT_STANDARD_PATH)] : paths;
9275
- for (const targetPathToWrite of orderedPaths) {
9276
- const destinationPath = path.join(projectRoot, targetPathToWrite);
9277
- const destinationKey = path.resolve(destinationPath);
9278
- if (seenDestinationPaths.has(destinationKey)) continue;
9279
- seenDestinationPaths.add(destinationKey);
9280
- await fsPromises.mkdir(path.dirname(destinationPath), { recursive: true });
9281
- if (shouldLinkToAgents && targetPathToWrite !== AGENT_STANDARD_PATH) {
9282
- if (await tryLinkTargetToAgents(projectRoot, targetPathToWrite, silent)) continue;
9283
- }
9284
- if (fs.existsSync(destinationPath)) {
9285
- if (fs.lstatSync(destinationPath).isSymbolicLink()) {
9286
- if (!silent) log.info(`Skipped writing ${targetPathToWrite} (symlink)`);
9287
- continue;
9288
- }
9289
- const destinationRealPath = await fsPromises.realpath(destinationPath);
9290
- if (seenRealPaths.has(destinationRealPath)) {
9291
- if (!silent) log.info(`Skipped writing ${targetPathToWrite} (duplicate target)`);
9292
- continue;
9293
- }
9294
- const existingContent = await fsPromises.readFile(destinationPath, "utf-8");
9295
- const updatedContent = replaceMarkedAgentInstructionsSection(existingContent, incomingContent);
9296
- if (updatedContent !== void 0) {
9297
- if (updatedContent !== existingContent) await fsPromises.writeFile(destinationPath, updatedContent);
9298
- seenRealPaths.add(destinationRealPath);
9299
- continue;
9300
- }
9377
+ async function writeEditorConfigs({ projectRoot, editorId, interactive, conflictDecisions, silent = false, extraVsCodeSettings }) {
9378
+ const editorIds = normalizeEditorSelection(editorId);
9379
+ if (editorIds.length === 0) return;
9380
+ for (const currentEditorId of editorIds) await writeEditorConfig({
9381
+ projectRoot,
9382
+ editorId: currentEditorId,
9383
+ interactive,
9384
+ conflictDecisions,
9385
+ silent,
9386
+ extraVsCodeSettings
9387
+ });
9388
+ }
9389
+ async function writeEditorConfig({ projectRoot, editorId, interactive, conflictDecisions, silent, extraVsCodeSettings }) {
9390
+ const editorConfig = EDITORS.find((e) => e.id === editorId);
9391
+ if (!editorConfig) return;
9392
+ const targetDir = path.join(projectRoot, editorConfig.targetDir);
9393
+ await fsPromises.mkdir(targetDir, { recursive: true });
9394
+ for (const [fileName, baseIncoming] of Object.entries(editorConfig.files)) {
9395
+ const incoming = editorId === "vscode" && fileName === "settings.json" && extraVsCodeSettings ? {
9396
+ ...extraVsCodeSettings,
9397
+ ...baseIncoming
9398
+ } : baseIncoming;
9399
+ const filePath = path.join(targetDir, fileName);
9400
+ if (fs.existsSync(filePath)) {
9401
+ const displayPath = `${editorConfig.targetDir}/${fileName}`;
9301
9402
  let conflictAction;
9302
- const preResolved = conflictDecisions?.get(targetPathToWrite);
9403
+ const preResolved = conflictDecisions?.get(displayPath) ?? conflictDecisions?.get(fileName);
9303
9404
  if (preResolved) conflictAction = preResolved;
9304
9405
  else if (interactive) {
9305
9406
  const action = await select({
9306
- message: `Agent instructions already exist at ${targetPathToWrite}.\n ` + styleText("gray", "The Vite+ template includes guidance on `vp` commands, the build pipeline, and project conventions."),
9407
+ message: `${displayPath} already exists.\n ` + styleText("gray", `Vite+ adds ${editorConfig.label} settings for the built-in linter and formatter. Merge adds new keys without overwriting existing ones.`),
9307
9408
  options: [{
9308
- label: "Append",
9309
- value: "append",
9310
- hint: "Add template content to the end"
9409
+ label: "Merge",
9410
+ value: "merge",
9411
+ hint: "Merge new settings into existing file"
9311
9412
  }, {
9312
9413
  label: "Skip",
9313
9414
  value: "skip",
@@ -9315,86 +9416,96 @@ async function writeAgentInstructions({ projectRoot, targetPath, targetPaths, in
9315
9416
  }],
9316
9417
  initialValue: "skip"
9317
9418
  });
9318
- conflictAction = isCancel(action) || action === "skip" ? "skip" : "append";
9319
- } else conflictAction = "skip";
9320
- if (conflictAction === "append") await appendAgentContent(destinationPath, targetPathToWrite, existingContent, incomingContent, silent);
9321
- else {
9322
- const suffix = !preResolved && !interactive ? " (already exists)" : "";
9323
- if (!silent) log.info(`Skipped writing ${targetPathToWrite}${suffix}`);
9324
- }
9325
- seenRealPaths.add(destinationRealPath);
9419
+ conflictAction = isCancel(action) || action === "skip" ? "skip" : "merge";
9420
+ } else conflictAction = "merge";
9421
+ if (conflictAction === "merge") mergeAndWriteEditorConfig(filePath, incoming, fileName, displayPath, silent);
9422
+ else if (!silent) log.info(`Skipped writing ${displayPath}`);
9326
9423
  continue;
9327
9424
  }
9328
- await fsPromises.writeFile(destinationPath, incomingContent);
9329
- if (!silent) log.success(`Wrote agent instructions to ${targetPathToWrite}`);
9330
- seenRealPaths.add(await fsPromises.realpath(destinationPath));
9425
+ writeJsonFile(filePath, incoming);
9426
+ if (!silent) log.success(`Wrote editor config to ${editorConfig.targetDir}/${fileName}`);
9331
9427
  }
9332
9428
  }
9333
- async function writeCopilotSetupWorkflow({ projectRoot, silent = false }) {
9334
- const destinationPath = path.join(projectRoot, COPILOT_SETUP_WORKFLOW_PATH);
9335
- await fsPromises.mkdir(path.dirname(destinationPath), { recursive: true });
9336
- if (fs.existsSync(destinationPath)) {
9337
- if (!silent) log.info(`Skipped writing ${COPILOT_SETUP_WORKFLOW_PATH} (already exists)`);
9429
+ function normalizeEditorSelection(editorId) {
9430
+ if (!editorId) return [];
9431
+ return [...new Set(Array.isArray(editorId) ? editorId : [editorId])];
9432
+ }
9433
+ /**
9434
+ * Merge incoming settings into an existing editor JSON/JSONC file by patching the
9435
+ * original text with `jsonc-parser` instead of re-serializing a merged object.
9436
+ * This preserves comments, key order, trailing commas, and untouched formatting.
9437
+ * Existing values always win; only missing keys/branches are inserted.
9438
+ */
9439
+ function mergeAndWriteEditorConfig(filePath, incoming, fileName, displayPath, silent = false) {
9440
+ const originalText = fs.readFileSync(filePath, "utf-8");
9441
+ const existing = parse(originalText);
9442
+ if (!isPlainObject(existing)) throw new Error(`Cannot merge editor config: ${displayPath} is not a JSON object`);
9443
+ const formattingOptions = detectFormattingOptions(originalText);
9444
+ const newText = fileName === "extensions.json" ? mergeExtensionsText(originalText, existing, incoming, formattingOptions) : mergeSettingsText(originalText, existing, incoming, formattingOptions);
9445
+ if (newText === originalText) {
9446
+ if (!silent) log.info(`No changes needed for ${displayPath}`);
9338
9447
  return;
9339
9448
  }
9340
- await fsPromises.writeFile(destinationPath, COPILOT_SETUP_WORKFLOW_CONTENT);
9341
- if (!silent) log.success(`Wrote Copilot setup workflow to ${COPILOT_SETUP_WORKFLOW_PATH}`);
9342
- }
9343
- async function appendAgentContent(destinationPath, targetPath, existingContent, incomingContent, silent = false) {
9344
- const separator = existingContent.endsWith("\n") ? "" : "\n";
9345
- await fsPromises.appendFile(destinationPath, `${separator}\n${incomingContent}`);
9346
- if (!silent) log.success(`Appended agent instructions to ${targetPath}`);
9347
- }
9348
- function normalizeAgentName(value) {
9349
- return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
9350
- }
9351
- function replaceMarkedAgentInstructionsSection(existing, incoming) {
9352
- const existingRange = getMarkedRange(existing, AGENT_INSTRUCTIONS_START_MARKER, AGENT_INSTRUCTIONS_END_MARKER);
9353
- if (!existingRange) return;
9354
- const incomingRange = getMarkedRange(incoming, AGENT_INSTRUCTIONS_START_MARKER, AGENT_INSTRUCTIONS_END_MARKER);
9355
- if (!incomingRange) return;
9356
- return `${existing.slice(0, existingRange.start)}${incoming.slice(incomingRange.start, incomingRange.end)}${existing.slice(existingRange.end)}`;
9357
- }
9358
- async function tryLinkTargetToAgents(projectRoot, targetPath, silent = false) {
9359
- const destinationPath = path.join(projectRoot, targetPath);
9360
- const agentsPath = path.join(projectRoot, AGENT_STANDARD_PATH);
9361
- const symlinkTarget = path.relative(path.dirname(destinationPath), agentsPath);
9362
- const existing = await getExistingPathKind(destinationPath);
9363
- if (existing === "file") return false;
9364
- if (existing === "symlink") {
9365
- const currentLink = await fsPromises.readlink(destinationPath);
9366
- if (path.resolve(path.dirname(destinationPath), currentLink) === agentsPath) {
9367
- if (!silent) log.info(`Skipped linking ${targetPath} (already linked to ${AGENT_STANDARD_PATH})`);
9368
- return true;
9369
- }
9370
- await fsPromises.unlink(destinationPath);
9371
- }
9372
- try {
9373
- await fsPromises.symlink(symlinkTarget, destinationPath);
9374
- } catch (err) {
9375
- if (err.code === "EPERM") {
9376
- await fsPromises.copyFile(agentsPath, destinationPath);
9377
- if (!silent) log.success(`Copied ${AGENT_STANDARD_PATH} to ${targetPath}`);
9378
- return true;
9449
+ fs.writeFileSync(filePath, newText, "utf-8");
9450
+ if (!silent) log.success(`Merged editor config into ${displayPath}`);
9451
+ }
9452
+ function isPlainObject(value) {
9453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9454
+ }
9455
+ /** Apply a single `jsonc-parser` modification to `text` and return the patched text. */
9456
+ function applyJsoncEdit(text, path, value, options) {
9457
+ return applyEdits(text, modify(text, path, value, options));
9458
+ }
9459
+ /**
9460
+ * Deep-merge missing keys from `incoming` into the existing text. Inserts a whole
9461
+ * branch when it is absent, and recurses only when both sides are non-array objects
9462
+ * so comments inside existing branches are preserved.
9463
+ */
9464
+ function mergeSettingsText(text, existing, incoming, formattingOptions) {
9465
+ let currentText = text;
9466
+ const insertMissing = (existingNode, incomingNode, basePath) => {
9467
+ for (const [key, value] of Object.entries(incomingNode)) {
9468
+ const fullPath = [...basePath, key];
9469
+ if (!(key in existingNode)) currentText = applyJsoncEdit(currentText, fullPath, value, { formattingOptions });
9470
+ else if (isPlainObject(existingNode[key]) && isPlainObject(value)) insertMissing(existingNode[key], value, fullPath);
9379
9471
  }
9380
- throw err;
9472
+ };
9473
+ insertMissing(existing, incoming, []);
9474
+ return currentText;
9475
+ }
9476
+ /**
9477
+ * For `extensions.json`, append missing recommendations without rebuilding the array,
9478
+ * so comments inside the array survive. Existing entries always win.
9479
+ */
9480
+ function mergeExtensionsText(text, existing, incoming, formattingOptions) {
9481
+ const incomingRecs = Array.isArray(incoming["recommendations"]) ? incoming["recommendations"] : [];
9482
+ const existingValue = existing["recommendations"];
9483
+ if (!("recommendations" in existing)) return applyJsoncEdit(text, ["recommendations"], incomingRecs, { formattingOptions });
9484
+ if (!Array.isArray(existingValue)) return text;
9485
+ const existingRecs = new Set(existingValue);
9486
+ let currentText = text;
9487
+ let nextIndex = existingValue.length;
9488
+ for (const rec of incomingRecs) {
9489
+ if (existingRecs.has(rec)) continue;
9490
+ currentText = applyJsoncEdit(currentText, ["recommendations", nextIndex], rec, {
9491
+ formattingOptions,
9492
+ isArrayInsertion: true
9493
+ });
9494
+ nextIndex++;
9381
9495
  }
9382
- if (!silent) log.success(`Linked ${targetPath} to ${AGENT_STANDARD_PATH}`);
9383
- return true;
9496
+ return currentText;
9384
9497
  }
9385
- async function getExistingPathKind(filePath) {
9386
- if (!fs.existsSync(filePath)) return "missing";
9387
- return (await fsPromises.lstat(filePath)).isSymbolicLink() ? "symlink" : "file";
9498
+ function resolveEditorId(editor) {
9499
+ const normalized = editor.trim().toLowerCase();
9500
+ return EDITORS.find((option) => option.id === normalized || option.label.toLowerCase() === normalized)?.id;
9388
9501
  }
9389
- function getMarkedRange(content, startMarker, endMarker) {
9390
- const start = content.indexOf(startMarker);
9391
- if (start === -1) return;
9392
- const endMarkerIndex = content.indexOf(endMarker, start + startMarker.length);
9393
- if (endMarkerIndex === -1) return;
9394
- return {
9395
- start,
9396
- end: endMarkerIndex + endMarker.length
9397
- };
9502
+ function resolveEditorIds(editors) {
9503
+ const editorIds = editors.flatMap((editor) => {
9504
+ const editorId = resolveEditorId(editor);
9505
+ return editorId ? [editorId] : [];
9506
+ });
9507
+ const uniqueEditorIds = [...new Set(editorIds)];
9508
+ return uniqueEditorIds.length === 0 ? void 0 : uniqueEditorIds;
9398
9509
  }
9399
9510
  //#endregion
9400
- export { globSync as A, warnPackageLevelEslint as B, injectLintTypeCheckDefaults as C, ensureVitePlusBootstrap as D, detectVitePlusBootstrapPending as E, migrateEslintToOxlint as F, checkVitestVersion as G, PRETTIER_PACKAGE_JSON_CONFIG as H, promptEslintMigration as I, templatesDir as J, warnMigration as K, rewriteToolLintStagedConfigFiles as L, confirmEslintMigration as M, detectEslintProject as N, configureYarnNodeModulesMode as O, detectIncompatibleEslintIntegration as P, warnIncompatibleEslintIntegration as R, injectCreateDefaultTemplate as S, collectToolchainVersionChanges as T, detectConfigs as U, PRETTIER_CONFIG_FILES as V, checkViteVersion as W, detectLegacyGitHooksMigrationCandidate as _, selectAgentTargets as a, preflightGitHooksSetup as b, writeCopilotSetupWorkflow as c, rewriteStandaloneProject as d, detectPendingCoreMigration as f, setPackageManager as g, migrateNodeVersionManagerFile as h, selectAgentTargetPaths as i, minimatch as j, detectYarnPnpMode as k, rewriteMonorepo as l, detectNodeVersionManagerFile as m, detectAgentConflicts as n, updateExistingAgentInstructions as o, finalizeCoreMigrationForExistingVitePlus as p, displayRelative as q, detectExistingAgentTargetPaths as r, writeAgentInstructions as s, COPILOT_AGENT_ID as t, rewriteMonorepoProject as u, ensurePreCommitHook as v, mergeViteConfigFiles as w, hasStagedConfigInViteConfig as x, installGitHooks as y, warnLegacyEslintConfig as z };
9511
+ export { addFrameworkShim as A, detectIncompatibleEslintIntegration as B, ensureVitePlusBootstrap as C, isBingoTemplate as D, detectWorkspace$1 as E, migratePrettierToOxfmt as F, warnPackageLevelEslint as G, promptEslintMigration as H, promptPrettierMigration as I, checkViteVersion as K, warnPackageLevelPrettier as L, hasFrameworkShim as M, confirmPrettierMigration as N, updatePackageJsonWithDeps as O, detectPrettierProject as P, confirmEslintMigration as R, detectVitePlusBootstrapPending as S, detectYarnPnpMode as T, warnIncompatibleEslintIntegration as U, migrateEslintToOxlint as V, warnLegacyEslintConfig as W, shouldSkipStagedMigrationForHooks as _, writeEditorConfigs as a, mergeViteConfigFiles as b, rewriteStandaloneProject as c, detectNodeVersionManagerFile as d, migrateNodeVersionManagerFile as f, preflightGitHooksSetup as g, installGitHooks as h, selectEditors as i, detectFramework as j, updateWorkspaceConfig as k, detectPendingCoreMigration as l, detectLegacyGitHooksMigrationCandidate as m, detectExistingEditors as n, rewriteMonorepo as o, setPackageManager as p, checkVitestVersion as q, selectEditor as r, rewriteMonorepoProject as s, detectEditorConflicts as t, finalizeCoreMigrationForExistingVitePlus as u, injectCreateDefaultTemplate as v, configureYarnNodeModulesMode as w, collectToolchainVersionChanges as x, injectLintTypeCheckDefaults as y, detectEslintProject as z };