Skip to content

Commit 8cb78cc

Browse files
committed
Added load command to sync repo with github.
1 parent 8a8acd3 commit 8cb78cc

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

src/__tests__/load.test.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
jest.mock('fs-extra', () => ({
2+
__esModule: true,
3+
default: {
4+
pathExists: jest.fn(),
5+
},
6+
}));
7+
8+
jest.mock('execa', () => ({
9+
__esModule: true,
10+
execa: jest.fn(),
11+
}));
12+
13+
import fs from 'fs-extra';
14+
import { execa } from 'execa';
15+
import { runLoad } from '../load.js';
16+
17+
const mockPathExists = fs.pathExists as jest.MockedFunction<typeof fs.pathExists>;
18+
const mockExeca = execa as jest.MockedFunction<typeof execa>;
19+
20+
const cwd = '/tmp/my-repo';
21+
22+
describe('runLoad', () => {
23+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
24+
const consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
25+
26+
beforeEach(() => {
27+
jest.clearAllMocks();
28+
});
29+
30+
afterAll(() => {
31+
consoleErrorSpy.mockRestore();
32+
consoleLogSpy.mockRestore();
33+
});
34+
35+
it('throws and logs error when .git directory does not exist', async () => {
36+
mockPathExists.mockResolvedValue(false);
37+
38+
await expect(runLoad(cwd)).rejects.toThrow('Not a git repository');
39+
expect(mockPathExists).toHaveBeenCalledWith(`${cwd}/.git`);
40+
expect(consoleErrorSpy).toHaveBeenCalledWith(
41+
expect.stringContaining('This directory is not a git repository'),
42+
);
43+
expect(mockExeca).not.toHaveBeenCalled();
44+
});
45+
46+
it('runs git pull and logs success when repo exists', async () => {
47+
mockPathExists.mockResolvedValue(true);
48+
mockExeca.mockResolvedValue({ stdout: '', stderr: '', exitCode: 0 } as Awaited<ReturnType<typeof execa>>);
49+
50+
await runLoad(cwd);
51+
52+
expect(mockExeca).toHaveBeenCalledTimes(1);
53+
expect(mockExeca).toHaveBeenCalledWith('git', ['pull'], {
54+
cwd,
55+
stdio: 'inherit',
56+
});
57+
expect(consoleLogSpy).toHaveBeenCalledWith(
58+
expect.stringContaining('Pulling latest updates from GitHub'),
59+
);
60+
expect(consoleLogSpy).toHaveBeenCalledWith(
61+
expect.stringContaining('Latest updates loaded successfully'),
62+
);
63+
});
64+
65+
it('throws and logs pull-failure message when pull fails with exitCode 128', async () => {
66+
mockPathExists.mockResolvedValue(true);
67+
mockExeca.mockRejectedValueOnce(Object.assign(new Error('pull failed'), { exitCode: 128 }));
68+
69+
await expect(runLoad(cwd)).rejects.toMatchObject({ message: 'pull failed' });
70+
71+
expect(consoleErrorSpy).toHaveBeenCalledWith(
72+
expect.stringContaining('Pull failed'),
73+
);
74+
expect(consoleErrorSpy).toHaveBeenCalledWith(
75+
expect.stringContaining('remote'),
76+
);
77+
});
78+
79+
it('throws and logs generic failure when pull fails with other exitCode', async () => {
80+
mockPathExists.mockResolvedValue(true);
81+
mockExeca.mockRejectedValueOnce(Object.assign(new Error('pull failed'), { exitCode: 1 }));
82+
83+
await expect(runLoad(cwd)).rejects.toMatchObject({ message: 'pull failed' });
84+
85+
expect(consoleErrorSpy).toHaveBeenCalledWith(
86+
expect.stringContaining('Pull failed'),
87+
);
88+
expect(consoleErrorSpy).toHaveBeenCalledWith(
89+
expect.stringContaining('See the output above'),
90+
);
91+
});
92+
93+
it('throws and logs error when execa throws without exitCode', async () => {
94+
mockPathExists.mockResolvedValue(true);
95+
mockExeca.mockRejectedValueOnce(new Error('network error'));
96+
97+
await expect(runLoad(cwd)).rejects.toThrow('network error');
98+
99+
expect(consoleErrorSpy).toHaveBeenCalledWith(
100+
expect.stringContaining('An error occurred'),
101+
);
102+
expect(consoleErrorSpy).toHaveBeenCalledWith(
103+
expect.stringContaining('network error'),
104+
);
105+
});
106+
});

src/cli.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { defaultTemplates } from './templates.js';
99
import { mergeTemplates } from './template-loader.js';
1010
import { checkGhAuth, createRepo, repoExists as ghRepoExists, sanitizeRepoName } from './github.js';
1111
import { runSave } from './save.js';
12+
import { runLoad } from './load.js';
1213

1314
/** Project data provided by the user */
1415
type ProjectData = {
@@ -351,4 +352,18 @@ program
351352
}
352353
});
353354

355+
/** Command to load latest updates from the remote */
356+
program
357+
.command('load')
358+
.description('Pull the latest updates from GitHub')
359+
.argument('[path]', 'Path to the repository (defaults to current directory)')
360+
.action(async (repoPath) => {
361+
const cwd = repoPath ? path.resolve(repoPath) : process.cwd();
362+
try {
363+
await runLoad(cwd);
364+
} catch {
365+
process.exit(1);
366+
}
367+
});
368+
354369
program.parse(process.argv);

src/load.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import path from 'path';
2+
import fs from 'fs-extra';
3+
import { execa } from 'execa';
4+
5+
/**
6+
* Runs the load flow: verify repo, then pull the latest updates from the remote.
7+
* Throws on fatal errors (not a git repo, or git command failure). Caller should catch and process.exit(1).
8+
*/
9+
export async function runLoad(cwd: string): Promise<void> {
10+
const gitDir = path.join(cwd, '.git');
11+
if (!(await fs.pathExists(gitDir))) {
12+
console.error('❌ This directory is not a git repository (.git not found).');
13+
console.error(' Initialize with "git init" or create a project with "patternfly-cli create".\n');
14+
throw new Error('Not a git repository');
15+
}
16+
17+
try {
18+
console.log('📥 Pulling latest updates from GitHub...\n');
19+
await execa('git', ['pull'], { cwd, stdio: 'inherit' });
20+
console.log('\n✅ Latest updates loaded successfully.\n');
21+
} catch (err) {
22+
if (err && typeof err === 'object' && 'exitCode' in err) {
23+
const code = (err as { exitCode?: number }).exitCode;
24+
if (code === 128) {
25+
console.error(
26+
'\n❌ Pull failed. You may need to set a remote (e.g. "git remote add origin <url>") or run "gh auth login".\n',
27+
);
28+
} else {
29+
console.error('\n❌ Pull failed. See the output above for details.\n');
30+
}
31+
} else {
32+
console.error('\n❌ An error occurred:');
33+
if (err instanceof Error) console.error(` ${err.message}\n`);
34+
else console.error(` ${String(err)}\n`);
35+
}
36+
throw err;
37+
}
38+
}

0 commit comments

Comments
 (0)