@@ -179,6 +179,213 @@ describe("createAdeCliService", () => {
179179 }
180180 } ) ;
181181
182+ it ( "adds the user install dir to the shell profile when installing Terminal access" , async ( ) => {
183+ const root = makeTempRoot ( ) ;
184+ const home = path . join ( root , "home" ) ;
185+ const resourcesPath = path . join ( root , "Resources" ) ;
186+ const packagedBinDir = path . join ( resourcesPath , "ade-cli" , "bin" ) ;
187+ const packagedCommandPath = path . join ( packagedBinDir , "ade" ) ;
188+ const installerPath = path . join ( resourcesPath , "ade-cli" , "install-path.sh" ) ;
189+ writeExecutable ( packagedCommandPath ) ;
190+ writeExecutable ( installerPath ) ;
191+ fs . writeFileSync ( path . join ( resourcesPath , "ade-cli" , "cli.cjs" ) , "console.log('ade')\n" ) ;
192+
193+ const service = createAdeCliService ( {
194+ isPackaged : true ,
195+ resourcesPath,
196+ userDataPath : path . join ( root , "user-data" ) ,
197+ appExecutablePath : path . join ( root , "ADE.app" , "Contents" , "MacOS" , "ADE" ) ,
198+ env : { HOME : home , SHELL : "/bin/zsh" , PATH : "/usr/bin:/bin" } ,
199+ logger : logger ( ) as any ,
200+ } ) ;
201+
202+ const result = await service . installForUser ( ) ;
203+ const profilePath = path . join ( home , ".zshrc" ) ;
204+ const profile = fs . readFileSync ( profilePath , "utf8" ) ;
205+
206+ expect ( result . ok ) . toBe ( true ) ;
207+ expect ( result . message ) . toContain ( `added ${ path . join ( home , ".local" , "bin" ) } to ${ profilePath } ` ) ;
208+ expect ( profile ) . toContain ( "# ADE CLI" ) ;
209+ expect ( profile ) . toContain ( 'export PATH="$HOME/.local/bin:$PATH"' ) ;
210+ } ) ;
211+
212+ it ( "writes to ~/.bashrc when SHELL is bash" , async ( ) => {
213+ const root = makeTempRoot ( ) ;
214+ const home = path . join ( root , "home" ) ;
215+ const resourcesPath = path . join ( root , "Resources" ) ;
216+ const packagedBinDir = path . join ( resourcesPath , "ade-cli" , "bin" ) ;
217+ writeExecutable ( path . join ( packagedBinDir , "ade" ) ) ;
218+ writeExecutable ( path . join ( resourcesPath , "ade-cli" , "install-path.sh" ) ) ;
219+ fs . writeFileSync ( path . join ( resourcesPath , "ade-cli" , "cli.cjs" ) , "console.log('ade')\n" ) ;
220+
221+ const service = createAdeCliService ( {
222+ isPackaged : true ,
223+ resourcesPath,
224+ userDataPath : path . join ( root , "user-data" ) ,
225+ appExecutablePath : path . join ( root , "ADE.app" , "Contents" , "MacOS" , "ADE" ) ,
226+ env : { HOME : home , SHELL : "/usr/local/bin/bash" , PATH : "/usr/bin:/bin" } ,
227+ logger : logger ( ) as any ,
228+ } ) ;
229+
230+ const result = await service . installForUser ( ) ;
231+ const profilePath = path . join ( home , ".bashrc" ) ;
232+
233+ expect ( result . ok ) . toBe ( true ) ;
234+ expect ( result . message ) . toContain ( profilePath ) ;
235+ expect ( fs . readFileSync ( profilePath , "utf8" ) ) . toContain ( 'export PATH="$HOME/.local/bin:$PATH"' ) ;
236+ } ) ;
237+
238+ it ( "falls back to ~/.profile when SHELL is unrecognized" , async ( ) => {
239+ const root = makeTempRoot ( ) ;
240+ const home = path . join ( root , "home" ) ;
241+ const resourcesPath = path . join ( root , "Resources" ) ;
242+ const packagedBinDir = path . join ( resourcesPath , "ade-cli" , "bin" ) ;
243+ writeExecutable ( path . join ( packagedBinDir , "ade" ) ) ;
244+ writeExecutable ( path . join ( resourcesPath , "ade-cli" , "install-path.sh" ) ) ;
245+ fs . writeFileSync ( path . join ( resourcesPath , "ade-cli" , "cli.cjs" ) , "console.log('ade')\n" ) ;
246+
247+ const service = createAdeCliService ( {
248+ isPackaged : true ,
249+ resourcesPath,
250+ userDataPath : path . join ( root , "user-data" ) ,
251+ appExecutablePath : path . join ( root , "ADE.app" , "Contents" , "MacOS" , "ADE" ) ,
252+ env : { HOME : home , SHELL : "/usr/bin/nu" , PATH : "/usr/bin:/bin" } ,
253+ logger : logger ( ) as any ,
254+ } ) ;
255+
256+ const result = await service . installForUser ( ) ;
257+ const profilePath = path . join ( home , ".profile" ) ;
258+
259+ expect ( result . ok ) . toBe ( true ) ;
260+ expect ( result . message ) . toContain ( profilePath ) ;
261+ expect ( fs . readFileSync ( profilePath , "utf8" ) ) . toContain ( 'export PATH="$HOME/.local/bin:$PATH"' ) ;
262+ } ) ;
263+
264+ it ( "writes fish-syntax PATH update to ~/.config/fish/config.fish for fish shell" , async ( ) => {
265+ const root = makeTempRoot ( ) ;
266+ const home = path . join ( root , "home" ) ;
267+ const resourcesPath = path . join ( root , "Resources" ) ;
268+ const packagedBinDir = path . join ( resourcesPath , "ade-cli" , "bin" ) ;
269+ writeExecutable ( path . join ( packagedBinDir , "ade" ) ) ;
270+ writeExecutable ( path . join ( resourcesPath , "ade-cli" , "install-path.sh" ) ) ;
271+ fs . writeFileSync ( path . join ( resourcesPath , "ade-cli" , "cli.cjs" ) , "console.log('ade')\n" ) ;
272+
273+ const service = createAdeCliService ( {
274+ isPackaged : true ,
275+ resourcesPath,
276+ userDataPath : path . join ( root , "user-data" ) ,
277+ appExecutablePath : path . join ( root , "ADE.app" , "Contents" , "MacOS" , "ADE" ) ,
278+ env : { HOME : home , SHELL : "/usr/bin/fish" , PATH : "/usr/bin:/bin" } ,
279+ logger : logger ( ) as any ,
280+ } ) ;
281+
282+ const result = await service . installForUser ( ) ;
283+ const profilePath = path . join ( home , ".config" , "fish" , "config.fish" ) ;
284+
285+ expect ( result . ok ) . toBe ( true ) ;
286+ expect ( result . message ) . toContain ( profilePath ) ;
287+ const profile = fs . readFileSync ( profilePath , "utf8" ) ;
288+ expect ( profile ) . toContain ( "# ADE CLI" ) ;
289+ expect ( profile ) . toContain ( "fish_add_path -gP $HOME/.local/bin" ) ;
290+ expect ( profile ) . not . toContain ( "export PATH=" ) ;
291+ } ) ;
292+
293+ it ( "skips the shell-profile write when the install dir is already on PATH" , async ( ) => {
294+ const root = makeTempRoot ( ) ;
295+ const home = path . join ( root , "home" ) ;
296+ const resourcesPath = path . join ( root , "Resources" ) ;
297+ const packagedBinDir = path . join ( resourcesPath , "ade-cli" , "bin" ) ;
298+ writeExecutable ( path . join ( packagedBinDir , "ade" ) ) ;
299+ writeExecutable ( path . join ( resourcesPath , "ade-cli" , "install-path.sh" ) ) ;
300+ fs . writeFileSync ( path . join ( resourcesPath , "ade-cli" , "cli.cjs" ) , "console.log('ade')\n" ) ;
301+ const targetDir = path . join ( home , ".local" , "bin" ) ;
302+ // Simulate an ade binary already at the install location so getStatus
303+ // reports it as installed once PATH contains targetDir.
304+ writeExecutable ( path . join ( targetDir , "ade" ) ) ;
305+
306+ const service = createAdeCliService ( {
307+ isPackaged : true ,
308+ resourcesPath,
309+ userDataPath : path . join ( root , "user-data" ) ,
310+ appExecutablePath : path . join ( root , "ADE.app" , "Contents" , "MacOS" , "ADE" ) ,
311+ env : { HOME : home , SHELL : "/bin/zsh" , PATH : `${ targetDir } :/usr/bin:/bin` } ,
312+ logger : logger ( ) as any ,
313+ } ) ;
314+
315+ const result = await service . installForUser ( ) ;
316+ const profilePath = path . join ( home , ".zshrc" ) ;
317+
318+ expect ( result . ok ) . toBe ( true ) ;
319+ expect ( result . message ) . toBe ( "Installed ade for Terminal access." ) ;
320+ expect ( fs . existsSync ( profilePath ) ) . toBe ( false ) ;
321+ } ) ;
322+
323+ it ( "does not append the PATH line twice when the marker is already present" , async ( ) => {
324+ const root = makeTempRoot ( ) ;
325+ const home = path . join ( root , "home" ) ;
326+ const resourcesPath = path . join ( root , "Resources" ) ;
327+ const packagedBinDir = path . join ( resourcesPath , "ade-cli" , "bin" ) ;
328+ writeExecutable ( path . join ( packagedBinDir , "ade" ) ) ;
329+ writeExecutable ( path . join ( resourcesPath , "ade-cli" , "install-path.sh" ) ) ;
330+ fs . writeFileSync ( path . join ( resourcesPath , "ade-cli" , "cli.cjs" ) , "console.log('ade')\n" ) ;
331+
332+ const profilePath = path . join ( home , ".zshrc" ) ;
333+ const seeded = "# previous user content\n\n# ADE CLI\nexport PATH=\"$HOME/.local/bin:$PATH\"\n" ;
334+ fs . mkdirSync ( home , { recursive : true } ) ;
335+ fs . writeFileSync ( profilePath , seeded ) ;
336+
337+ const service = createAdeCliService ( {
338+ isPackaged : true ,
339+ resourcesPath,
340+ userDataPath : path . join ( root , "user-data" ) ,
341+ appExecutablePath : path . join ( root , "ADE.app" , "Contents" , "MacOS" , "ADE" ) ,
342+ env : { HOME : home , SHELL : "/bin/zsh" , PATH : "/usr/bin:/bin" } ,
343+ logger : logger ( ) as any ,
344+ } ) ;
345+
346+ const result = await service . installForUser ( ) ;
347+
348+ expect ( result . ok ) . toBe ( true ) ;
349+ expect ( result . message ) . toContain ( profilePath ) ;
350+ expect ( result . message ) . toContain ( "PATH entry already present" ) ;
351+ expect ( result . message ) . not . toMatch ( / a n d a d d e d .* t o / ) ;
352+ // Profile contents are unchanged — exactly one ADE CLI marker, exactly one PATH line.
353+ const profile = fs . readFileSync ( profilePath , "utf8" ) ;
354+ expect ( profile ) . toBe ( seeded ) ;
355+ expect ( profile . match ( / # A D E C L I / g) ?. length ) . toBe ( 1 ) ;
356+ } ) ;
357+
358+ it ( "inserts a leading newline when the existing profile has no trailing newline" , async ( ) => {
359+ const root = makeTempRoot ( ) ;
360+ const home = path . join ( root , "home" ) ;
361+ const resourcesPath = path . join ( root , "Resources" ) ;
362+ const packagedBinDir = path . join ( resourcesPath , "ade-cli" , "bin" ) ;
363+ writeExecutable ( path . join ( packagedBinDir , "ade" ) ) ;
364+ writeExecutable ( path . join ( resourcesPath , "ade-cli" , "install-path.sh" ) ) ;
365+ fs . writeFileSync ( path . join ( resourcesPath , "ade-cli" , "cli.cjs" ) , "console.log('ade')\n" ) ;
366+
367+ const profilePath = path . join ( home , ".zshrc" ) ;
368+ fs . mkdirSync ( home , { recursive : true } ) ;
369+ fs . writeFileSync ( profilePath , "alias foo=bar" ) ; // no trailing newline
370+
371+ const service = createAdeCliService ( {
372+ isPackaged : true ,
373+ resourcesPath,
374+ userDataPath : path . join ( root , "user-data" ) ,
375+ appExecutablePath : path . join ( root , "ADE.app" , "Contents" , "MacOS" , "ADE" ) ,
376+ env : { HOME : home , SHELL : "/bin/zsh" , PATH : "/usr/bin:/bin" } ,
377+ logger : logger ( ) as any ,
378+ } ) ;
379+
380+ const result = await service . installForUser ( ) ;
381+ expect ( result . ok ) . toBe ( true ) ;
382+
383+ const profile = fs . readFileSync ( profilePath , "utf8" ) ;
384+ expect ( profile . startsWith ( "alias foo=bar\n" ) ) . toBe ( true ) ;
385+ expect ( profile ) . toContain ( "\n# ADE CLI\n" ) ;
386+ expect ( profile ) . toContain ( 'export PATH="$HOME/.local/bin:$PATH"\n' ) ;
387+ } ) ;
388+
182389 it ( "creates a dev shim under userData without changing global PATH" , ( ) => {
183390 const root = makeTempRoot ( ) ;
184391 const repoRoot = path . join ( root , "repo" ) ;
0 commit comments