1+ import * as fs from 'node:fs/promises' ;
12import { program } from 'commander' ;
23import { afterEach , beforeEach , describe , expect , test , vi } from 'vitest' ;
34import * as defaultAction from '../../src/cli/actions/defaultAction.js' ;
@@ -6,6 +7,7 @@ import * as remoteAction from '../../src/cli/actions/remoteAction.js';
67import * as versionAction from '../../src/cli/actions/versionAction.js' ;
78import { run , runCli } from '../../src/cli/cliRun.js' ;
89import type { CliOptions } from '../../src/cli/types.js' ;
10+ import * as gitRemoteHandle from '../../src/core/git/gitRemoteHandle.js' ;
911import type { PackResult } from '../../src/core/packager.js' ;
1012import { logger , type RepomixLogLevel , repomixLogLevels } from '../../src/shared/logger.js' ;
1113import { createMockConfig } from '../testing/testUtils.js' ;
@@ -40,11 +42,25 @@ vi.mock('../../src/shared/logger', () => ({
4042vi . mock ( '../../src/cli/actions/defaultAction' ) ;
4143vi . mock ( '../../src/cli/actions/initAction' ) ;
4244vi . mock ( '../../src/cli/actions/remoteAction' ) ;
45+ vi . mock ( '../../src/core/git/gitRemoteHandle' ) ;
4346vi . mock ( '../../src/cli/actions/versionAction' ) ;
4447
48+ // Partial mock: `access` is spyable for shorthand-detection tests, everything else stays real.
49+ vi . mock ( 'node:fs/promises' , async ( importOriginal ) => {
50+ const actual = await importOriginal < typeof import ( 'node:fs/promises' ) > ( ) ;
51+ return {
52+ ...actual ,
53+ access : vi . fn ( actual . access ) ,
54+ } ;
55+ } ) ;
56+
57+ const actualFs = await vi . importActual < typeof import ( 'node:fs/promises' ) > ( 'node:fs/promises' ) ;
58+
4559describe ( 'cliRun' , ( ) => {
4660 beforeEach ( ( ) => {
4761 vi . resetAllMocks ( ) ;
62+ // resetAllMocks clears the default implementation — restore real fs.access behavior.
63+ vi . mocked ( fs . access ) . mockImplementation ( actualFs . access ) ;
4864
4965 vi . mocked ( defaultAction . runDefaultAction ) . mockResolvedValue ( {
5066 config : createMockConfig ( {
@@ -241,13 +257,66 @@ describe('cliRun', () => {
241257 expect ( defaultAction . runDefaultAction ) . not . toHaveBeenCalled ( ) ;
242258 } ) ;
243259
244- test ( 'should not auto-detect shorthand format as remote URL' , async ( ) => {
260+ test ( 'should auto-detect shorthand when no local path exists and the repository is reachable' , async ( ) => {
261+ vi . mocked ( gitRemoteHandle . checkRemoteRepoExists ) . mockResolvedValue ( true ) ;
262+
263+ await runCli ( [ 'user/repo' ] , process . cwd ( ) , { } ) ;
264+
265+ expect ( gitRemoteHandle . checkRemoteRepoExists ) . toHaveBeenCalledWith ( 'https://github.com/user/repo.git' ) ;
266+ expect ( remoteAction . runRemoteAction ) . toHaveBeenCalledWith ( 'user/repo' , expect . any ( Object ) ) ;
267+ expect ( defaultAction . runDefaultAction ) . not . toHaveBeenCalled ( ) ;
268+ } ) ;
269+
270+ test ( 'should fall back to local handling when shorthand is not a reachable repository' , async ( ) => {
271+ vi . mocked ( gitRemoteHandle . checkRemoteRepoExists ) . mockResolvedValue ( false ) ;
272+
273+ await runCli ( [ 'user/repo' ] , process . cwd ( ) , { } ) ;
274+
275+ expect ( gitRemoteHandle . checkRemoteRepoExists ) . toHaveBeenCalledWith ( 'https://github.com/user/repo.git' ) ;
276+ expect ( defaultAction . runDefaultAction ) . toHaveBeenCalledWith ( [ 'user/repo' ] , process . cwd ( ) , expect . any ( Object ) ) ;
277+ expect ( remoteAction . runRemoteAction ) . not . toHaveBeenCalled ( ) ;
278+ } ) ;
279+
280+ test ( 'should prefer existing local path over shorthand auto-detection' , async ( ) => {
281+ // Simulate an existing local path that also matches the owner/repo pattern.
282+ // Mocking fs.access keeps the test independent of the repository's own directory layout.
283+ vi . mocked ( fs . access ) . mockResolvedValue ( undefined ) ;
284+
245285 await runCli ( [ 'user/repo' ] , process . cwd ( ) , { } ) ;
246286
287+ expect ( gitRemoteHandle . checkRemoteRepoExists ) . not . toHaveBeenCalled ( ) ;
247288 expect ( defaultAction . runDefaultAction ) . toHaveBeenCalledWith ( [ 'user/repo' ] , process . cwd ( ) , expect . any ( Object ) ) ;
248289 expect ( remoteAction . runRemoteAction ) . not . toHaveBeenCalled ( ) ;
249290 } ) ;
250291
292+ test ( 'should treat permission-denied local path as existing and skip the remote probe' , async ( ) => {
293+ const accessError = Object . assign ( new Error ( 'permission denied' ) , { code : 'EACCES' } ) ;
294+ vi . mocked ( fs . access ) . mockRejectedValue ( accessError ) ;
295+
296+ await runCli ( [ 'user/repo' ] , process . cwd ( ) , { } ) ;
297+
298+ expect ( gitRemoteHandle . checkRemoteRepoExists ) . not . toHaveBeenCalled ( ) ;
299+ expect ( remoteAction . runRemoteAction ) . not . toHaveBeenCalled ( ) ;
300+ expect ( defaultAction . runDefaultAction ) . toHaveBeenCalledWith ( [ 'user/repo' ] , process . cwd ( ) , expect . any ( Object ) ) ;
301+ } ) ;
302+
303+ test ( 'should not treat Windows-style absolute path as shorthand' , async ( ) => {
304+ // `C:` contains a colon, which the owner/repo pattern rejects — no probe even when missing locally.
305+ await runCli ( [ 'C:/project' ] , process . cwd ( ) , { } ) ;
306+
307+ expect ( gitRemoteHandle . checkRemoteRepoExists ) . not . toHaveBeenCalled ( ) ;
308+ expect ( remoteAction . runRemoteAction ) . not . toHaveBeenCalled ( ) ;
309+ expect ( defaultAction . runDefaultAction ) . toHaveBeenCalledWith ( [ 'C:/project' ] , process . cwd ( ) , expect . any ( Object ) ) ;
310+ } ) ;
311+
312+ test ( 'should not probe shorthand in stdin mode' , async ( ) => {
313+ await runCli ( [ 'user/repo' ] , process . cwd ( ) , { stdin : true } ) ;
314+
315+ expect ( gitRemoteHandle . checkRemoteRepoExists ) . not . toHaveBeenCalled ( ) ;
316+ expect ( remoteAction . runRemoteAction ) . not . toHaveBeenCalled ( ) ;
317+ expect ( defaultAction . runDefaultAction ) . toHaveBeenCalledWith ( [ 'user/repo' ] , process . cwd ( ) , expect . any ( Object ) ) ;
318+ } ) ;
319+
251320 test ( 'should prioritize explicit --remote flag over auto-detected URL' , async ( ) => {
252321 await runCli ( [ 'https://github.com/other/repo' ] , process . cwd ( ) , {
253322 remote : 'yamadashy/repomix' ,
0 commit comments