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.
- package/LICENSE +5 -1
- package/bin/vpr +3 -2
- package/binding/index.cjs +193 -80
- package/binding/index.d.cts +99 -8
- package/dist/agent-CI79DuSy.js +600 -0
- package/dist/bin.js +1368 -13
- package/dist/{cli-truncate-BVOlIIPy.js → cli-truncate-Bg3RDXpi.js} +0 -2
- package/dist/client/bundledDevClient.mjs +1 -0
- package/dist/config/bin.js +13 -126
- package/dist/{constants-BsuzPowC.js → constants-BppDcRc1.js} +1 -1
- package/dist/create/bin.js +90 -77
- package/dist/{define-config-DLh9fE95.cjs → define-config-CEm_MtvJ.cjs} +1 -1
- package/dist/{define-config-C2KfLHpC.js → define-config-DPnHe1Mx.js} +2 -2
- package/dist/{define-config-eLQH9us2.d.ts → define-config-DWoAkNdp.d.ts} +16 -0
- package/dist/define-config.cjs +1 -1
- package/dist/define-config.d.ts +1 -1
- package/dist/define-config.js +1 -1
- package/dist/{dist-CKz3vcoG.js → dist-CSQWHI6y.js} +3 -8
- package/dist/dist-DT25H9pj.js +3 -0
- package/dist/{agent-CPwWJ4qf.js → editor-CGWdbLgD.js} +690 -579
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{json-DiRs8ceZ.js → json-BU88uu6o.js} +2 -6
- package/dist/migration/bin.js +58 -48
- package/dist/{oxlint-plugin-config-Bxgc4cwF.js → oxlint-plugin-config-BHOzlwMw.js} +1 -1
- package/dist/oxlint-plugin.js +1 -1
- package/dist/pack-bin.js +1 -1
- package/dist/{package-Ds3uThLb.js → package-hV-77cBb.js} +2 -2
- package/dist/{tsconfig-Jia2vgMG.js → prompts-DYap08te.js} +32 -174
- package/dist/{resolve-vite-config-BiuLZnn8.js → resolve-vite-config-EWXclqvV.js} +1 -1
- package/dist/{rolldown-runtime-C7HZzL1F.js → rolldown-runtime-CMFfr-1z.js} +1 -1
- package/dist/staged/bin.js +20 -23
- package/dist/{help-YP84FSEz.js → terminal-CrqqK8WT.js} +39 -2
- package/dist/tsconfig-CVUKLfL8.js +151 -0
- package/dist/{tsgolint-path-B-yOos8p.js → tsgolint-path-eMZT-oea.js} +4 -2
- package/dist/tsgolint-path.js +1 -1
- package/dist/version.js +5 -5
- package/dist/versions.js +4 -4
- package/docs/config/index.md +13 -0
- package/docs/config/staged.md +2 -1
- package/docs/guide/commit-hooks.md +38 -20
- package/docs/guide/env.md +8 -4
- package/docs/guide/index.md +1 -1
- package/docs/guide/install.md +2 -0
- package/docs/guide/installer-env-vars.md +3 -3
- package/docs/guide/migrate.md +10 -2
- package/docs/guide/monorepo.md +94 -6
- package/docs/guide/troubleshooting.md +3 -2
- package/package.json +17 -23
- package/dist/dist-CtM2JRbM.js +0 -3
- package/dist/editor-BbT1lTNc.js +0 -675
- package/dist/terminal-uTv0ZaMr.js +0 -33
- 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 };
|