Skip to content

Commit 2a45901

Browse files
committed
fix: Tighten vp create behavior related to agent file handling.
1 parent 322af13 commit 2a45901

4 files changed

Lines changed: 80 additions & 22 deletions

File tree

packages/cli/src/create/bin.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from '../migration/migrator.js';
1414
import { DependencyType, type WorkspaceInfo } from '../types/index.js';
1515
import {
16-
detectExistingAgentTargetPath,
16+
detectExistingAgentTargetPaths,
1717
selectAgentTargetPaths,
1818
writeAgentInstructions,
1919
} from '../utils/agent.js';
@@ -493,25 +493,28 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
493493
// Prompt for package manager or use default
494494
const packageManager =
495495
workspaceInfoOptional.packageManager ?? (await selectPackageManager(options.interactive));
496+
const shouldSilencePackageManagerInstallLog =
497+
isMonorepo && workspaceInfoOptional.packageManager !== undefined;
496498
// ensure the package manager is installed by vite-plus
497499
const downloadResult = await downloadPackageManager(
498500
packageManager,
499501
workspaceInfoOptional.packageManagerVersion,
500502
options.interactive,
503+
shouldSilencePackageManagerInstallLog,
501504
);
502505
const workspaceInfo: WorkspaceInfo = {
503506
...workspaceInfoOptional,
504507
packageManager,
505508
downloadPackageManager: downloadResult,
506509
};
507510

508-
const existingAgentTargetPath =
511+
const existingAgentTargetPaths =
509512
options.agent !== undefined || !options.interactive
510513
? undefined
511-
: detectExistingAgentTargetPath(workspaceInfoOptional.rootDir);
514+
: detectExistingAgentTargetPaths(workspaceInfoOptional.rootDir);
512515
selectedAgentTargetPaths =
513-
existingAgentTargetPath !== undefined
514-
? [existingAgentTargetPath]
516+
existingAgentTargetPaths !== undefined
517+
? existingAgentTargetPaths
515518
: await selectAgentTargetPaths({
516519
interactive: options.interactive,
517520
agent: options.agent,
@@ -628,8 +631,9 @@ Use \`vp create --list\` to list all available templates, or run \`vp create --h
628631

629632
prompts.log.success(`Project directory: ${accent(projectDir)}`);
630633
const fullPath = path.join(workspaceInfo.rootDir, projectDir);
634+
const agentInstructionsRoot = isMonorepo ? workspaceInfo.rootDir : fullPath;
631635
await writeAgentInstructions({
632-
projectRoot: fullPath,
636+
projectRoot: agentInstructionsRoot,
633637
targetPaths: selectedAgentTargetPaths,
634638
interactive: options.interactive,
635639
});

packages/cli/src/utils/__tests__/agent.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as prompts from '@voidzero-dev/vite-plus-prompts';
66
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
77

88
import {
9+
detectExistingAgentTargetPaths,
910
detectExistingAgentTargetPath,
1011
replaceMarkedAgentInstructionsSection,
1112
resolveAgentTargetPaths,
@@ -259,6 +260,14 @@ describe('resolveAgentTargetPaths', () => {
259260
});
260261

261262
describe('detectExistingAgentTargetPath', () => {
263+
it('detects all existing regular agent files', async () => {
264+
const dir = await createProjectDir();
265+
await mockFs.writeFile(path.join(dir, 'AGENTS.md'), '# Agents');
266+
await mockFs.writeFile(path.join(dir, 'CLAUDE.md'), '# Claude');
267+
268+
expect(detectExistingAgentTargetPaths(dir)).toEqual(['AGENTS.md', 'CLAUDE.md']);
269+
});
270+
262271
it('detects existing regular agent files', async () => {
263272
const dir = await createProjectDir();
264273
await mockFs.writeFile(path.join(dir, 'CLAUDE.md'), '# Claude');
@@ -340,4 +349,29 @@ describe('writeAgentInstructions symlink behavior', () => {
340349
expect(await mockFs.readText(existingClaude)).toBe('existing claude instructions');
341350
expect(mockFs.existsSync(path.join(dir, 'AGENTS.md'))).toBe(true);
342351
});
352+
353+
it('silently updates marker blocks without prompting in interactive mode', async () => {
354+
const dir = await createProjectDir();
355+
const targetPath = path.join(dir, 'AGENTS.md');
356+
const existing = [
357+
'# Local',
358+
'<!--VITE PLUS START-->',
359+
'old block',
360+
'<!--VITE PLUS END-->',
361+
].join('\n');
362+
await mockFs.writeFile(targetPath, existing);
363+
364+
const selectSpy = vi.spyOn(prompts, 'select');
365+
const successSpy = vi.spyOn(prompts.log, 'success');
366+
367+
await writeAgentInstructions({
368+
projectRoot: dir,
369+
targetPaths: ['AGENTS.md'],
370+
interactive: true,
371+
});
372+
373+
expect(selectSpy).not.toHaveBeenCalled();
374+
expect(await mockFs.readText(targetPath)).toContain('template block');
375+
expect(successSpy).not.toHaveBeenCalledWith('Updated agent instructions in AGENTS.md');
376+
});
343377
});

packages/cli/src/utils/agent.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,24 @@ export async function selectAgentTargetPath({
245245
return targetPaths?.[0];
246246
}
247247

248-
export function detectExistingAgentTargetPath(projectRoot: string) {
248+
export function detectExistingAgentTargetPaths(projectRoot: string) {
249+
const detectedPaths: string[] = [];
250+
const seenTargetPaths = new Set<string>();
249251
for (const option of AGENTS) {
252+
if (seenTargetPaths.has(option.targetPath)) {
253+
continue;
254+
}
255+
seenTargetPaths.add(option.targetPath);
250256
const targetPath = path.join(projectRoot, option.targetPath);
251257
if (fs.existsSync(targetPath) && !fs.lstatSync(targetPath).isSymbolicLink()) {
252-
return option.targetPath;
258+
detectedPaths.push(option.targetPath);
253259
}
254260
}
255-
return undefined;
261+
return detectedPaths.length > 0 ? detectedPaths : undefined;
262+
}
263+
264+
export function detectExistingAgentTargetPath(projectRoot: string) {
265+
return detectExistingAgentTargetPaths(projectRoot)?.[0];
256266
}
257267

258268
export function resolveAgentTargetPaths(agent?: string | string[]) {
@@ -357,6 +367,19 @@ export async function writeAgentInstructions({
357367
continue;
358368
}
359369

370+
const existingContent = await fsPromises.readFile(destinationPath, 'utf-8');
371+
const updatedContent = replaceMarkedAgentInstructionsSection(
372+
existingContent,
373+
incomingContent,
374+
);
375+
if (updatedContent !== undefined) {
376+
if (updatedContent !== existingContent) {
377+
await fsPromises.writeFile(destinationPath, updatedContent);
378+
}
379+
seenRealPaths.add(destinationRealPath);
380+
continue;
381+
}
382+
360383
if (interactive) {
361384
const action = await prompts.select({
362385
message: `Agent instructions already exist at ${targetPathToWrite}.`,
@@ -380,18 +403,6 @@ export async function writeAgentInstructions({
380403
continue;
381404
}
382405

383-
const existingContent = await fsPromises.readFile(destinationPath, 'utf-8');
384-
const updatedContent = replaceMarkedAgentInstructionsSection(
385-
existingContent,
386-
incomingContent,
387-
);
388-
if (updatedContent !== undefined) {
389-
await fsPromises.writeFile(destinationPath, updatedContent);
390-
prompts.log.success(`Updated agent instructions in ${targetPathToWrite}`);
391-
seenRealPaths.add(destinationRealPath);
392-
continue;
393-
}
394-
395406
const separator = existingContent.endsWith('\n') ? '' : '\n';
396407
await fsPromises.appendFile(destinationPath, `${separator}\n${incomingContent}`);
397408
prompts.log.success(`Appended agent instructions to ${targetPathToWrite}`);

packages/cli/src/utils/prompts.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ export async function downloadPackageManager(
3939
packageManager: PackageManager,
4040
version: string,
4141
interactive?: boolean,
42+
silent = false,
4243
) {
43-
const spinner = getSpinner(interactive);
44+
const spinner = silent ? getSilentSpinner() : getSpinner(interactive);
4445
spinner.start(`${packageManager}@${version} installing...`);
4546
const downloadResult = await downloadPackageManagerBinding({
4647
name: packageManager,
@@ -170,3 +171,11 @@ export function getSpinner(interactive?: boolean) {
170171
},
171172
};
172173
}
174+
175+
function getSilentSpinner() {
176+
return {
177+
start: () => {},
178+
stop: () => {},
179+
message: () => {},
180+
};
181+
}

0 commit comments

Comments
 (0)