1- import { describe , it , expect } from "bun:test" ;
2- import { getFilesForTemplate } from "./init" ;
3- import { render , BLINK_COMMAND , makeTmpDir , KEY_CODES } from "./lib/terminal" ;
1+ import { describe , it , expect , mock } from "bun:test" ;
2+ import { getFilesForTemplate , getAvailablePackageManagers } from "./init" ;
3+ import {
4+ render ,
5+ BLINK_COMMAND ,
6+ makeTmpDir ,
7+ KEY_CODES ,
8+ pathToCliEntrypoint ,
9+ } from "./lib/terminal" ;
410import { join } from "path" ;
5- import { readFile } from "fs/promises" ;
11+ import { readFile , writeFile , chmod , mkdir } from "fs/promises" ;
12+ import { execSync } from "child_process" ;
613
714const getFile = ( files : Record < string , string > , filename : string ) : string => {
815 const fileContent = files [ filename ] ;
@@ -241,10 +248,9 @@ describe("init command", () => {
241248 screen . includes ( "What package manager do you want to use?" )
242249 ) ;
243250 const screen = term . getScreen ( ) ;
244- expect ( screen ) . toContain ( "Bun" ) ;
245- expect ( screen ) . toContain ( "NPM" ) ;
246- expect ( screen ) . toContain ( "PNPM" ) ;
247- expect ( screen ) . toContain ( "Yarn" ) ;
251+ // At least one package manager should be available in the test environment
252+ // We don't check for all of them since they may not be installed
253+ expect ( screen . includes ( "Bun" ) ) . toBe ( true ) ;
248254 term . write ( KEY_CODES . ENTER ) ;
249255 await term . waitUntil ( ( screen ) =>
250256 screen . includes ( "API key saved to .env.local" )
@@ -254,4 +260,119 @@ describe("init command", () => {
254260 const envFileContent = await readFile ( envFilePath , "utf-8" ) ;
255261 expect ( envFileContent . split ( "\n" ) ) . toContain ( "OPENAI_API_KEY=sk-test-123" ) ;
256262 } ) ;
263+
264+ describe ( "package manager detection" , ( ) => {
265+ async function setupMockPackageManagers (
266+ packageManagers : Array < "bun" | "npm" | "pnpm" | "yarn" >
267+ ) : Promise < AsyncDisposable & { binDir : string ; PATH : string } > {
268+ const tmpDir = await makeTmpDir ( ) ;
269+ const binDir = join ( tmpDir . path , "bin" ) ;
270+ await mkdir ( binDir ) ;
271+
272+ const allPackageManagers = [ "bun" , "npm" , "pnpm" , "yarn" ] as const ;
273+
274+ // Create dummy executables for each package manager
275+ for ( const pm of allPackageManagers ) {
276+ const scriptPath = join ( binDir , pm ) ;
277+ if ( packageManagers . includes ( pm ) ) {
278+ // Create working mock for available package managers
279+ await writeFile ( scriptPath , `#!/bin/sh\nexit 0\n` , "utf-8" ) ;
280+ } else {
281+ // Create failing mock for unavailable package managers
282+ await writeFile ( scriptPath , `#!/bin/sh\nexit 1\n` , "utf-8" ) ;
283+ }
284+ await chmod ( scriptPath , 0o755 ) ;
285+ }
286+
287+ // Prepend our bin directory to PATH so our mocks are found first,
288+ // but keep the rest of PATH so system commands like 'script' still work
289+ const newPath = `${ binDir } :${ process . env . PATH || "" } ` ;
290+
291+ return {
292+ binDir,
293+ PATH : newPath ,
294+ [ Symbol . asyncDispose ] : ( ) => tmpDir [ Symbol . asyncDispose ] ( ) ,
295+ } ;
296+ }
297+
298+ const absoluteBunPath = execSync ( "which bun" ) . toString ( ) . trim ( ) ;
299+
300+ async function navigateToPackageManagerPrompt (
301+ PATH : string
302+ ) : Promise < AsyncDisposable & { screen : string } > {
303+ const tempDir = await makeTmpDir ( ) ;
304+ using term = render ( `${ absoluteBunPath } ${ pathToCliEntrypoint } init` , {
305+ cwd : tempDir . path ,
306+ env : { ...process . env , PATH } ,
307+ } ) ;
308+
309+ // Navigate through prompts to package manager selection
310+ await term . waitUntil ( ( screen ) => screen . includes ( "Scratch" ) ) ;
311+ term . write ( KEY_CODES . DOWN ) ;
312+ await term . waitUntil ( ( screen ) =>
313+ screen . includes ( "Basic agent with example tool" )
314+ ) ;
315+ term . write ( KEY_CODES . ENTER ) ;
316+
317+ await term . waitUntil ( ( screen ) =>
318+ screen . includes ( "Which AI provider do you want to use?" )
319+ ) ;
320+ term . write ( KEY_CODES . ENTER ) ;
321+
322+ await term . waitUntil ( ( screen ) =>
323+ screen . includes ( "Enter your OpenAI API key:" )
324+ ) ;
325+ term . write ( KEY_CODES . ENTER ) ; // Skip API key
326+
327+ // Wait for either package manager prompt or manual install message
328+ await term . waitUntil (
329+ ( screen ) =>
330+ screen . includes ( "What package manager do you want to use?" ) ||
331+ screen . includes ( "Please install dependencies by running:" )
332+ ) ;
333+
334+ return {
335+ screen : term . getScreen ( ) ,
336+ [ Symbol . asyncDispose ] : ( ) => tempDir [ Symbol . asyncDispose ] ( ) ,
337+ } ;
338+ }
339+
340+ it ( "should show all package managers when all are available" , async ( ) => {
341+ await using mockPms = await setupMockPackageManagers ( [
342+ "bun" ,
343+ "npm" ,
344+ "pnpm" ,
345+ "yarn" ,
346+ ] ) ;
347+ await using result = await navigateToPackageManagerPrompt ( mockPms . PATH ) ;
348+
349+ // All package managers should be available
350+ expect ( result . screen ) . toContain ( "Bun" ) ;
351+ expect ( result . screen ) . toContain ( "NPM" ) ;
352+ expect ( result . screen ) . toContain ( "PNPM" ) ;
353+ expect ( result . screen ) . toContain ( "Yarn" ) ;
354+ } ) ;
355+
356+ it ( "should show only bun and npm when only they are available" , async ( ) => {
357+ await using mockPms = await setupMockPackageManagers ( [ "bun" , "npm" ] ) ;
358+ await using result = await navigateToPackageManagerPrompt ( mockPms . PATH ) ;
359+
360+ // Only bun and npm should be available
361+ expect ( result . screen ) . toContain ( "Bun" ) ;
362+ expect ( result . screen ) . toContain ( "NPM" ) ;
363+ expect ( result . screen ) . not . toContain ( "PNPM" ) ;
364+ expect ( result . screen ) . not . toContain ( "Yarn" ) ;
365+ } ) ;
366+
367+ it ( "should show manual install message when no package managers are available" , async ( ) => {
368+ await using mockPms = await setupMockPackageManagers ( [ ] ) ;
369+ await using result = await navigateToPackageManagerPrompt ( mockPms . PATH ) ;
370+
371+ // Should show manual install message instead of package manager selection
372+ expect ( result . screen ) . toContain ( "npm install" ) ;
373+ expect ( result . screen ) . not . toContain (
374+ "What package manager do you want to use?"
375+ ) ;
376+ } ) ;
377+ } ) ;
257378} ) ;
0 commit comments