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
@@ -0,0 +1,600 @@
1
+ import { A as isCancel, C as log, E as select, w as multiselect } from "./prompts-DYap08te.js";
2
+ import path, { isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
3
+ import { styleText } from "node:util";
4
+ import fs, { chmodSync, lstatSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
5
+ import fsPromises from "node:fs/promises";
6
+ import { spawnSync } from "node:child_process";
7
+ //#region src/utils/path.ts
8
+ function findPkgRoot() {
9
+ let dir = import.meta.dirname;
10
+ while (dir !== path.dirname(dir)) {
11
+ if (fs.existsSync(path.join(dir, "package.json"))) return dir;
12
+ dir = path.dirname(dir);
13
+ }
14
+ return dir;
15
+ }
16
+ const pkgRoot = findPkgRoot();
17
+ const templatesDir = path.join(pkgRoot, "templates");
18
+ const rulesDir = path.join(pkgRoot, "rules");
19
+ function displayRelative(to, from = process.cwd()) {
20
+ return path.relative(from, to).replaceAll("\\", "/");
21
+ }
22
+ //#endregion
23
+ //#region src/config/hooks.ts
24
+ const SUPPORTED_GIT_HOOK_NAMES = [
25
+ "pre-commit",
26
+ "pre-merge-commit",
27
+ "prepare-commit-msg",
28
+ "commit-msg",
29
+ "post-commit",
30
+ "applypatch-msg",
31
+ "pre-applypatch",
32
+ "post-applypatch",
33
+ "pre-rebase",
34
+ "post-rewrite",
35
+ "post-checkout",
36
+ "post-merge",
37
+ "pre-push",
38
+ "pre-auto-gc"
39
+ ];
40
+ function nestedDirname(depth) {
41
+ let expr = "\"$0\"";
42
+ for (let i = 0; i < depth; i++) expr = `"$(dirname ${expr})"`;
43
+ return expr;
44
+ }
45
+ function hookScript(dir) {
46
+ const separators = process.platform === "win32" ? /[\\/]/ : /\//;
47
+ return `#!/usr/bin/env sh
48
+ { [ "$HUSKY" = "2" ] || [ "$VP_GIT_HOOKS" = "2" ] || [ "$VITE_GIT_HOOKS" = "2" ]; } && set -x
49
+ n=$(basename "$0")
50
+ s=$(dirname "$(dirname "$0")")/$n
51
+
52
+ [ ! -f "$s" ] && exit 0
53
+
54
+ i="\${XDG_CONFIG_HOME:-$HOME/.config}/vite-plus/hooks-init.sh"
55
+ [ ! -f "$i" ] && i="\${XDG_CONFIG_HOME:-$HOME/.config}/husky/init.sh"
56
+ [ -f "$i" ] && . "$i"
57
+
58
+ { [ "\${HUSKY-}" = "0" ] || [ "\${VP_GIT_HOOKS-}" = "0" ] || [ "\${VITE_GIT_HOOKS-}" = "0" ]; } && exit 0
59
+
60
+ d=${nestedDirname(dir.split(separators).filter((s) => s !== "" && s !== ".").length + 2)}
61
+ __vp_shell=/bin/sh
62
+ [ -x "$__vp_shell" ] || __vp_shell=$(command -v sh)
63
+
64
+ if [ -n "\${VP_HOME-}" ]; then
65
+ __vp_bin="$VP_HOME/bin"
66
+ elif [ -n "\${HOME-}" ]; then
67
+ __vp_bin="$HOME/.vite-plus/bin"
68
+ else
69
+ __vp_bin=""
70
+ fi
71
+ [ -n "$__vp_bin" ] && [ -d "$__vp_bin" ] && export PATH="$PATH:$__vp_bin"
72
+
73
+ export PATH="$d/node_modules/.bin:$PATH"
74
+ "$__vp_shell" -e "$s" "$@"
75
+ c=$?
76
+
77
+ [ $c != 0 ] && echo "VITE+ - $n script failed (code $c)"
78
+ [ $c = 127 ] && echo "VITE+ - command not found in PATH=$PATH"
79
+ exit $c`;
80
+ }
81
+ function normalizeHooksPath(hooksPath) {
82
+ let normalized = normalize(hooksPath);
83
+ while (normalized.endsWith(sep)) normalized = normalized.slice(0, -1);
84
+ return normalized;
85
+ }
86
+ function findUnsafeHookInstallPath(root, dir) {
87
+ const projectRoot = resolve(root);
88
+ const internalPath = resolve(projectRoot, dir, "_");
89
+ const relativeInternalPath = relative(projectRoot, internalPath);
90
+ let currentPath = projectRoot;
91
+ for (const component of relativeInternalPath.split(sep).filter(Boolean)) {
92
+ currentPath = join(currentPath, component);
93
+ const stats = lstatSync(currentPath, { throwIfNoEntry: false });
94
+ if (!stats) return null;
95
+ if (stats.isSymbolicLink()) return {
96
+ kind: "symbolic",
97
+ relativePath: relative(projectRoot, currentPath)
98
+ };
99
+ if (!stats.isDirectory()) return {
100
+ kind: "not-directory",
101
+ relativePath: relative(projectRoot, currentPath)
102
+ };
103
+ }
104
+ for (const filename of [
105
+ "husky.sh",
106
+ ".gitignore",
107
+ "h",
108
+ ...SUPPORTED_GIT_HOOK_NAMES
109
+ ]) {
110
+ const filePath = join(internalPath, filename);
111
+ const stats = lstatSync(filePath, { throwIfNoEntry: false });
112
+ if (!stats) continue;
113
+ if (stats.isSymbolicLink()) return {
114
+ kind: "symbolic",
115
+ relativePath: relative(projectRoot, filePath)
116
+ };
117
+ if (!stats.isFile()) return {
118
+ kind: "not-file",
119
+ relativePath: relative(projectRoot, filePath)
120
+ };
121
+ if (stats.nlink > 1) return {
122
+ kind: "linked",
123
+ relativePath: relative(projectRoot, filePath)
124
+ };
125
+ }
126
+ return null;
127
+ }
128
+ function describeUnsafeHookInstallPath(unsafePath) {
129
+ if (unsafePath.kind === "symbolic") return `symbolic hook path "${unsafePath.relativePath}" not allowed`;
130
+ if (unsafePath.kind === "linked") return `multiply linked hook path "${unsafePath.relativePath}" not allowed`;
131
+ if (unsafePath.kind === "not-directory") return `hook path "${unsafePath.relativePath}" is not a directory`;
132
+ return `hook path "${unsafePath.relativePath}" is not a file`;
133
+ }
134
+ function install(dir = ".vite-hooks") {
135
+ if (process.env.HUSKY === "0" || process.env.VP_GIT_HOOKS === "0" || process.env.VITE_GIT_HOOKS === "0") return {
136
+ message: "skip install (git hooks disabled)",
137
+ isError: false
138
+ };
139
+ if (dir.includes("..")) return {
140
+ message: ".. not allowed",
141
+ isError: false
142
+ };
143
+ if (isAbsolute(dir)) return {
144
+ message: "absolute hooks directory not allowed",
145
+ isError: false
146
+ };
147
+ if (relative(process.cwd(), resolve(process.cwd(), dir)) === "") return {
148
+ message: "hooks directory must be a project subdirectory",
149
+ isError: false
150
+ };
151
+ const unsafeInstallPath = findUnsafeHookInstallPath(process.cwd(), dir);
152
+ if (unsafeInstallPath) return {
153
+ message: describeUnsafeHookInstallPath(unsafeInstallPath),
154
+ isError: false
155
+ };
156
+ const prefixResult = spawnSync("git", ["rev-parse", "--show-prefix"]);
157
+ if (prefixResult.status == null) return {
158
+ message: "git command not found",
159
+ isError: true
160
+ };
161
+ if (prefixResult.status !== 0) return {
162
+ message: ".git can't be found",
163
+ isError: false
164
+ };
165
+ const internal = (x = "") => join(dir, "_", x);
166
+ const rel = prefixResult.stdout.toString().trim().replace(/\/$/, "");
167
+ const target = rel ? `${rel}/${dir}/_` : `${dir}/_`;
168
+ const checkResult = spawnSync("git", [
169
+ "config",
170
+ "--get",
171
+ "core.hooksPath"
172
+ ]);
173
+ const existingHooksPath = checkResult.status === 0 ? checkResult.stdout?.toString().trim() : "";
174
+ if (existingHooksPath && normalizeHooksPath(existingHooksPath) !== normalizeHooksPath(target)) return {
175
+ message: `core.hooksPath is already set to "${existingHooksPath}", skipping`,
176
+ isError: false
177
+ };
178
+ rmSync(internal("husky.sh"), { force: true });
179
+ mkdirSync(internal(), { recursive: true });
180
+ writeFileSync(internal(".gitignore"), "*");
181
+ writeFileSync(internal("h"), hookScript(dir), { mode: 493 });
182
+ chmodSync(internal("h"), 493);
183
+ for (const hook of SUPPORTED_GIT_HOOK_NAMES) {
184
+ writeFileSync(internal(hook), `#!/usr/bin/env sh\n. "$(dirname "$0")/h"`, { mode: 493 });
185
+ chmodSync(internal(hook), 493);
186
+ }
187
+ const { status, stderr } = spawnSync("git", [
188
+ "config",
189
+ "core.hooksPath",
190
+ target
191
+ ]);
192
+ if (status == null) return {
193
+ message: "git command not found",
194
+ isError: true
195
+ };
196
+ if (status) return {
197
+ message: "" + stderr,
198
+ isError: true
199
+ };
200
+ return {
201
+ message: "",
202
+ isError: false
203
+ };
204
+ }
205
+ //#endregion
206
+ //#region src/utils/agent.ts
207
+ const AGENTS = [
208
+ {
209
+ id: "agents",
210
+ label: "AGENTS.md",
211
+ targetPath: "AGENTS.md",
212
+ hint: "Codex, Amp, OpenCode, and similar agents",
213
+ aliases: [
214
+ "agents.md",
215
+ "chatgpt",
216
+ "chatgpt-codex",
217
+ "codex",
218
+ "amp",
219
+ "kilo",
220
+ "kilo-code",
221
+ "kiro",
222
+ "kiro-cli",
223
+ "opencode",
224
+ "other"
225
+ ]
226
+ },
227
+ {
228
+ id: "claude",
229
+ label: "CLAUDE.md",
230
+ targetPath: "CLAUDE.md",
231
+ hint: "Claude Code",
232
+ aliases: ["claude.md", "claude-code"]
233
+ },
234
+ {
235
+ id: "gemini",
236
+ label: "GEMINI.md",
237
+ targetPath: "GEMINI.md",
238
+ hint: "Gemini CLI",
239
+ aliases: ["gemini.md", "gemini-cli"]
240
+ },
241
+ {
242
+ id: "copilot",
243
+ label: ".github/copilot-instructions.md",
244
+ targetPath: ".github/copilot-instructions.md",
245
+ hint: "GitHub Copilot",
246
+ aliases: ["github-copilot", "copilot-instructions.md"]
247
+ },
248
+ {
249
+ id: "cursor",
250
+ label: ".cursor/rules/viteplus.mdc",
251
+ targetPath: ".cursor/rules/viteplus.mdc",
252
+ hint: "Cursor",
253
+ aliases: ["viteplus.mdc"]
254
+ },
255
+ {
256
+ id: "jetbrains",
257
+ label: ".aiassistant/rules/viteplus.md",
258
+ targetPath: ".aiassistant/rules/viteplus.md",
259
+ hint: "JetBrains AI Assistant",
260
+ aliases: [
261
+ "jetbrains",
262
+ "jetbrains-ai-assistant",
263
+ "aiassistant",
264
+ "viteplus.md"
265
+ ]
266
+ }
267
+ ];
268
+ const AGENT_DEFAULT_ID = "agents";
269
+ const AGENT_STANDARD_PATH = "AGENTS.md";
270
+ const COPILOT_AGENT_ID = "copilot";
271
+ const COPILOT_SETUP_WORKFLOW_PATH = ".github/workflows/copilot-setup-steps.yml";
272
+ const AGENT_INSTRUCTIONS_START_MARKER = "<!--VITE PLUS START-->";
273
+ const AGENT_INSTRUCTIONS_END_MARKER = "<!--VITE PLUS END-->";
274
+ const AGENT_ALIASES = Object.fromEntries(AGENTS.flatMap((option) => (option.aliases ?? []).map((alias) => [normalizeAgentName(alias), option.id])));
275
+ const COPILOT_SETUP_WORKFLOW_CONTENT = `name: "Copilot Setup Steps"
276
+
277
+ on:
278
+ workflow_dispatch:
279
+ push:
280
+ paths:
281
+ - .github/workflows/copilot-setup-steps.yml
282
+ pull_request:
283
+ paths:
284
+ - .github/workflows/copilot-setup-steps.yml
285
+
286
+ jobs:
287
+ copilot-setup-steps:
288
+ runs-on: ubuntu-latest
289
+ permissions:
290
+ contents: read
291
+ steps:
292
+ - name: Checkout code
293
+ uses: actions/checkout@v6
294
+ with:
295
+ persist-credentials: false
296
+ - name: Set up Vite+
297
+ uses: voidzero-dev/setup-vp@v1
298
+ with:
299
+ cache: true
300
+ run-install: true
301
+ - name: Verify Vite+
302
+ run: vp --version
303
+ `;
304
+ async function selectAgentTargets({ interactive, agent, onCancel }) {
305
+ if (agent === false) return {
306
+ targetPaths: void 0,
307
+ selectedAgents: []
308
+ };
309
+ if (interactive && !agent) {
310
+ const selectedAgentIds = await multiselect({
311
+ message: "Which coding agent instruction files should Vite+ create?",
312
+ options: AGENTS.map((option) => ({
313
+ label: option.label,
314
+ value: option.id,
315
+ hint: option.hint
316
+ })),
317
+ initialValues: [AGENT_DEFAULT_ID],
318
+ required: false
319
+ });
320
+ if (isCancel(selectedAgentIds)) {
321
+ onCancel();
322
+ return {
323
+ targetPaths: void 0,
324
+ selectedAgents: []
325
+ };
326
+ }
327
+ if (selectedAgentIds.length === 0) return {
328
+ targetPaths: void 0,
329
+ selectedAgents: []
330
+ };
331
+ const selectedAgents = resolveAgentOptions(selectedAgentIds);
332
+ return {
333
+ targetPaths: getAgentTargetPaths(selectedAgents),
334
+ selectedAgents
335
+ };
336
+ }
337
+ const selectedAgents = resolveAgentOptions(agent ?? AGENT_DEFAULT_ID);
338
+ return {
339
+ targetPaths: getAgentTargetPaths(selectedAgents),
340
+ selectedAgents
341
+ };
342
+ }
343
+ async function selectAgentTargetPaths({ interactive, agent, onCancel }) {
344
+ return (await selectAgentTargets({
345
+ interactive,
346
+ agent,
347
+ onCancel
348
+ })).targetPaths;
349
+ }
350
+ function detectExistingAgentTargetPaths(projectRoot) {
351
+ const detectedPaths = [];
352
+ const seenTargetPaths = /* @__PURE__ */ new Set();
353
+ for (const option of AGENTS) {
354
+ if (seenTargetPaths.has(option.targetPath)) continue;
355
+ seenTargetPaths.add(option.targetPath);
356
+ const targetPath = path.join(projectRoot, option.targetPath);
357
+ if (fs.existsSync(targetPath) && !fs.lstatSync(targetPath).isSymbolicLink()) detectedPaths.push(option.targetPath);
358
+ }
359
+ return detectedPaths.length > 0 ? detectedPaths : void 0;
360
+ }
361
+ /**
362
+ * Silently update agent instruction files that contain Vite+ markers.
363
+ * - No agent files → no writes
364
+ * - No Vite+ markers → no writes
365
+ * - Markers present, content up to date → no writes
366
+ * - Markers present, content outdated → update marked section
367
+ */
368
+ function updateExistingAgentInstructions(projectRoot) {
369
+ const targetPaths = detectExistingAgentTargetPaths(projectRoot);
370
+ if (!targetPaths) return;
371
+ const templatePath = path.join(pkgRoot, "AGENTS.md");
372
+ if (!fs.existsSync(templatePath)) return;
373
+ const templateContent = fs.readFileSync(templatePath, "utf-8");
374
+ for (const targetPath of targetPaths) try {
375
+ const fullPath = path.join(projectRoot, targetPath);
376
+ const existing = fs.readFileSync(fullPath, "utf-8");
377
+ const updated = replaceMarkedAgentInstructionsSection(existing, templateContent);
378
+ if (updated !== void 0 && updated !== existing) fs.writeFileSync(fullPath, updated);
379
+ } catch {}
380
+ }
381
+ function resolveAgentOptions(agent) {
382
+ const agentNames = parseAgentNames(agent);
383
+ const resolvedAgentNames = agentNames.length > 0 ? agentNames : [AGENT_DEFAULT_ID];
384
+ const dedupedAgents = [];
385
+ const seenAgentIds = /* @__PURE__ */ new Set();
386
+ for (const name of resolvedAgentNames) {
387
+ const option = resolveSingleAgentOption(name);
388
+ if (seenAgentIds.has(option.id)) continue;
389
+ seenAgentIds.add(option.id);
390
+ dedupedAgents.push(option);
391
+ }
392
+ return dedupedAgents;
393
+ }
394
+ function getAgentTargetPaths(agents) {
395
+ const dedupedTargetPaths = [];
396
+ const seenTargetPaths = /* @__PURE__ */ new Set();
397
+ for (const agent of agents) {
398
+ if (seenTargetPaths.has(agent.targetPath)) continue;
399
+ seenTargetPaths.add(agent.targetPath);
400
+ dedupedTargetPaths.push(agent.targetPath);
401
+ }
402
+ return dedupedTargetPaths;
403
+ }
404
+ function parseAgentNames(agent) {
405
+ if (!agent) return [];
406
+ return (Array.isArray(agent) ? agent : [agent]).filter((value) => typeof value === "string").flatMap((value) => value.split(",")).map((value) => value.trim()).filter((value) => value.length > 0);
407
+ }
408
+ function resolveSingleAgentOption(agent) {
409
+ const normalized = normalizeAgentName(agent);
410
+ const alias = AGENT_ALIASES[normalized];
411
+ const resolved = alias ? normalizeAgentName(alias) : normalized;
412
+ 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);
413
+ }
414
+ /** Order target paths for the shared detect/write traversal: AGENTS.md first. */
415
+ function orderAgentTargetPaths(projectRoot, targetPaths) {
416
+ const orderedPaths = targetPaths.includes(AGENT_STANDARD_PATH) ? [AGENT_STANDARD_PATH, ...targetPaths.filter((p) => p !== AGENT_STANDARD_PATH)] : targetPaths;
417
+ const dedupedPaths = [];
418
+ const seenDestinationPaths = /* @__PURE__ */ new Set();
419
+ for (const targetPath of orderedPaths) {
420
+ const destinationKey = path.resolve(path.join(projectRoot, targetPath));
421
+ if (seenDestinationPaths.has(destinationKey)) continue;
422
+ seenDestinationPaths.add(destinationKey);
423
+ dedupedPaths.push(targetPath);
424
+ }
425
+ return dedupedPaths;
426
+ }
427
+ /**
428
+ * Classify an existing agent instruction file for the shared detect/write
429
+ * traversal. Registers the file's realpath in the caller-owned `seenRealPaths`.
430
+ */
431
+ async function classifyExistingAgentTarget(destinationPath, incomingContent, seenRealPaths) {
432
+ if (fs.lstatSync(destinationPath).isSymbolicLink()) return { kind: "symlink" };
433
+ const destinationRealPath = await fsPromises.realpath(destinationPath);
434
+ if (seenRealPaths.has(destinationRealPath)) return { kind: "duplicate" };
435
+ seenRealPaths.add(destinationRealPath);
436
+ const existingContent = await fsPromises.readFile(destinationPath, "utf-8");
437
+ const updatedContent = replaceMarkedAgentInstructionsSection(existingContent, incomingContent);
438
+ if (updatedContent !== void 0) return {
439
+ kind: "markers",
440
+ existingContent,
441
+ updatedContent
442
+ };
443
+ return {
444
+ kind: "conflict",
445
+ existingContent
446
+ };
447
+ }
448
+ /**
449
+ * Detect agent instruction files that would conflict (exist without markers).
450
+ * Returns only files that need a user decision (append or skip).
451
+ * Read-only — does not write or modify any files.
452
+ */
453
+ async function detectAgentConflicts({ projectRoot, targetPaths }) {
454
+ if (!targetPaths || targetPaths.length === 0) return [];
455
+ const sourcePath = path.join(pkgRoot, "AGENTS.md");
456
+ if (!fs.existsSync(sourcePath)) return [];
457
+ const incomingContent = await fsPromises.readFile(sourcePath, "utf-8");
458
+ const shouldLinkToAgents = targetPaths.includes(AGENT_STANDARD_PATH);
459
+ const conflicts = [];
460
+ const seenRealPaths = /* @__PURE__ */ new Set();
461
+ for (const targetPathToCheck of orderAgentTargetPaths(projectRoot, targetPaths)) {
462
+ const destinationPath = path.join(projectRoot, targetPathToCheck);
463
+ if (shouldLinkToAgents && targetPathToCheck !== AGENT_STANDARD_PATH) {
464
+ if (await getExistingPathKind(destinationPath) !== "file") continue;
465
+ }
466
+ if (!fs.existsSync(destinationPath)) continue;
467
+ if ((await classifyExistingAgentTarget(destinationPath, incomingContent, seenRealPaths)).kind === "conflict") conflicts.push({ targetPath: targetPathToCheck });
468
+ }
469
+ return conflicts;
470
+ }
471
+ async function writeAgentInstructions({ projectRoot, targetPath, targetPaths, interactive, conflictDecisions, silent = false }) {
472
+ const paths = [...targetPaths ?? [], ...targetPath ? [targetPath] : []];
473
+ if (paths.length === 0) return;
474
+ const sourcePath = path.join(pkgRoot, "AGENTS.md");
475
+ if (!fs.existsSync(sourcePath)) {
476
+ if (!silent) log.warn("Agent instructions template not found; skipping.");
477
+ return;
478
+ }
479
+ const seenRealPaths = /* @__PURE__ */ new Set();
480
+ const incomingContent = await fsPromises.readFile(sourcePath, "utf-8");
481
+ const shouldLinkToAgents = paths.includes(AGENT_STANDARD_PATH);
482
+ for (const targetPathToWrite of orderAgentTargetPaths(projectRoot, paths)) {
483
+ const destinationPath = path.join(projectRoot, targetPathToWrite);
484
+ await fsPromises.mkdir(path.dirname(destinationPath), { recursive: true });
485
+ if (shouldLinkToAgents && targetPathToWrite !== AGENT_STANDARD_PATH) {
486
+ if (await tryLinkTargetToAgents(projectRoot, targetPathToWrite, silent)) continue;
487
+ }
488
+ if (fs.existsSync(destinationPath)) {
489
+ const state = await classifyExistingAgentTarget(destinationPath, incomingContent, seenRealPaths);
490
+ if (state.kind === "symlink") {
491
+ if (!silent) log.info(`Skipped writing ${targetPathToWrite} (symlink)`);
492
+ continue;
493
+ }
494
+ if (state.kind === "duplicate") {
495
+ if (!silent) log.info(`Skipped writing ${targetPathToWrite} (duplicate target)`);
496
+ continue;
497
+ }
498
+ if (state.kind === "markers") {
499
+ if (state.updatedContent !== state.existingContent) await fsPromises.writeFile(destinationPath, state.updatedContent);
500
+ continue;
501
+ }
502
+ let conflictAction;
503
+ const preResolved = conflictDecisions?.get(targetPathToWrite);
504
+ if (preResolved) conflictAction = preResolved;
505
+ else if (interactive) {
506
+ const action = await select({
507
+ message: `Agent instructions already exist at ${targetPathToWrite}.\n ` + styleText("gray", "The Vite+ template includes guidance on `vp` commands, the build pipeline, and project conventions."),
508
+ options: [{
509
+ label: "Append",
510
+ value: "append",
511
+ hint: "Add template content to the end"
512
+ }, {
513
+ label: "Skip",
514
+ value: "skip",
515
+ hint: "Leave existing file unchanged"
516
+ }],
517
+ initialValue: "skip"
518
+ });
519
+ conflictAction = isCancel(action) || action === "skip" ? "skip" : "append";
520
+ } else conflictAction = "skip";
521
+ if (conflictAction === "append") await appendAgentContent(destinationPath, targetPathToWrite, state.existingContent, incomingContent, silent);
522
+ else {
523
+ const suffix = !preResolved && !interactive ? " (already exists)" : "";
524
+ if (!silent) log.info(`Skipped writing ${targetPathToWrite}${suffix}`);
525
+ }
526
+ continue;
527
+ }
528
+ await fsPromises.writeFile(destinationPath, incomingContent);
529
+ if (!silent) log.success(`Wrote agent instructions to ${targetPathToWrite}`);
530
+ seenRealPaths.add(await fsPromises.realpath(destinationPath));
531
+ }
532
+ }
533
+ async function writeCopilotSetupWorkflow({ projectRoot, silent = false }) {
534
+ const destinationPath = path.join(projectRoot, COPILOT_SETUP_WORKFLOW_PATH);
535
+ await fsPromises.mkdir(path.dirname(destinationPath), { recursive: true });
536
+ if (fs.existsSync(destinationPath)) {
537
+ if (!silent) log.info(`Skipped writing ${COPILOT_SETUP_WORKFLOW_PATH} (already exists)`);
538
+ return;
539
+ }
540
+ await fsPromises.writeFile(destinationPath, COPILOT_SETUP_WORKFLOW_CONTENT);
541
+ if (!silent) log.success(`Wrote Copilot setup workflow to ${COPILOT_SETUP_WORKFLOW_PATH}`);
542
+ }
543
+ async function appendAgentContent(destinationPath, targetPath, existingContent, incomingContent, silent = false) {
544
+ const separator = existingContent.endsWith("\n") ? "" : "\n";
545
+ await fsPromises.appendFile(destinationPath, `${separator}\n${incomingContent}`);
546
+ if (!silent) log.success(`Appended agent instructions to ${targetPath}`);
547
+ }
548
+ function normalizeAgentName(value) {
549
+ return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
550
+ }
551
+ function replaceMarkedAgentInstructionsSection(existing, incoming) {
552
+ const existingRange = getMarkedRange(existing, AGENT_INSTRUCTIONS_START_MARKER, AGENT_INSTRUCTIONS_END_MARKER);
553
+ if (!existingRange) return;
554
+ const incomingRange = getMarkedRange(incoming, AGENT_INSTRUCTIONS_START_MARKER, AGENT_INSTRUCTIONS_END_MARKER);
555
+ if (!incomingRange) return;
556
+ return `${existing.slice(0, existingRange.start)}${incoming.slice(incomingRange.start, incomingRange.end)}${existing.slice(existingRange.end)}`;
557
+ }
558
+ async function tryLinkTargetToAgents(projectRoot, targetPath, silent = false) {
559
+ const destinationPath = path.join(projectRoot, targetPath);
560
+ const agentsPath = path.join(projectRoot, AGENT_STANDARD_PATH);
561
+ const symlinkTarget = path.relative(path.dirname(destinationPath), agentsPath);
562
+ const existing = await getExistingPathKind(destinationPath);
563
+ if (existing === "file") return false;
564
+ if (existing === "symlink") {
565
+ const currentLink = await fsPromises.readlink(destinationPath);
566
+ if (path.resolve(path.dirname(destinationPath), currentLink) === agentsPath) {
567
+ if (!silent) log.info(`Skipped linking ${targetPath} (already linked to ${AGENT_STANDARD_PATH})`);
568
+ return true;
569
+ }
570
+ await fsPromises.unlink(destinationPath);
571
+ }
572
+ try {
573
+ await fsPromises.symlink(symlinkTarget, destinationPath);
574
+ } catch (err) {
575
+ if (err.code === "EPERM") {
576
+ await fsPromises.copyFile(agentsPath, destinationPath);
577
+ if (!silent) log.success(`Copied ${AGENT_STANDARD_PATH} to ${targetPath}`);
578
+ return true;
579
+ }
580
+ throw err;
581
+ }
582
+ if (!silent) log.success(`Linked ${targetPath} to ${AGENT_STANDARD_PATH}`);
583
+ return true;
584
+ }
585
+ async function getExistingPathKind(filePath) {
586
+ if (!fs.existsSync(filePath)) return "missing";
587
+ return (await fsPromises.lstat(filePath)).isSymbolicLink() ? "symlink" : "file";
588
+ }
589
+ function getMarkedRange(content, startMarker, endMarker) {
590
+ const start = content.indexOf(startMarker);
591
+ if (start === -1) return;
592
+ const endMarkerIndex = content.indexOf(endMarker, start + startMarker.length);
593
+ if (endMarkerIndex === -1) return;
594
+ return {
595
+ start,
596
+ end: endMarkerIndex + endMarker.length
597
+ };
598
+ }
599
+ //#endregion
600
+ export { selectAgentTargets as a, writeCopilotSetupWorkflow as c, install as d, displayRelative as f, selectAgentTargetPaths as i, SUPPORTED_GIT_HOOK_NAMES as l, templatesDir as m, detectAgentConflicts as n, updateExistingAgentInstructions as o, rulesDir as p, detectExistingAgentTargetPaths as r, writeAgentInstructions as s, COPILOT_AGENT_ID as t, findUnsafeHookInstallPath as u };