diff --git a/CLAUDE.md b/CLAUDE.md index 07743a821..d7a394bd6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -67,6 +67,13 @@ Node / Express / Mongoose / JWT stack from Devkit. Standalone backend or fullsta - PRs: always use `/pull-request` — never open manually - After user correction, evaluate if the pattern belongs in `ERRORS.md` +## Workflow rules + +- **Never push directly to master/main.** Always create a branch, push, create a PR, wait for CI green + review, then merge. +- **Never lower coverage thresholds** in `jest.config.js`. If coverage drops after adding code, write tests for the new project modules to bring it back above thresholds. +- **Audit existing modules before implementing.** Before creating new storage, file handling, or utility code, check `modules/` for existing solutions (e.g., `uploads` module for file storage via GridFS). +- **Always run `/verify` after any code change** before declaring done. CI must be green. + ## Skills | Skill | Description | diff --git a/modules/home/services/home.service.js b/modules/home/services/home.service.js index 52cb55341..0a2d7bed8 100644 --- a/modules/home/services/home.service.js +++ b/modules/home/services/home.service.js @@ -32,21 +32,25 @@ const page = async (name) => { * @return {Promise} All versions */ const releases = async () => { - const requests = config.repos.map((item) => - axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/releases`, { - headers: item.token ? { Authorization: `token ${item.token}` } : {}, - }), - ); - let results = await axios.all(requests); - results = results.map((result, i) => ({ - title: config.repos[i].title, - list: result.data.map((release) => ({ - name: release.name, - prerelease: release.prerelease, - published_at: release.published_at, - })), - })); - return Promise.resolve(results); + try { + const requests = config.repos.map((item) => + axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/releases`, { + headers: item.token ? { Authorization: `token ${item.token}` } : {}, + }), + ); + let results = await axios.all(requests); + results = results.map((result, i) => ({ + title: config.repos[i].title, + list: result.data.map((release) => ({ + name: release.name, + prerelease: release.prerelease, + published_at: release.published_at, + })), + })); + return results; + } catch (_err) { + return []; + } }; /** @@ -54,18 +58,22 @@ const releases = async () => { * @return {Promise} All changelogs */ const changelogs = async () => { - const repos = _.filter(config.repos, (repo) => repo.changelog); - const requests = repos.map((item) => - axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/contents/${item.changelog}`, { - headers: item.token ? { Authorization: `token ${item.token}` } : {}, - }), - ); - let results = await axios.all(requests); - results = results.map((result, i) => ({ - title: config.repos[i].title, - markdown: Base64.decode(result.data.content), - })); - return Promise.resolve(results); + try { + const repos = _.filter(config.repos, (repo) => repo.changelog); + const requests = repos.map((item) => + axios.get(`https://api.github.com/repos/${item.owner}/${item.repo}/contents/${item.changelog}`, { + headers: item.token ? { Authorization: `token ${item.token}` } : {}, + }), + ); + let results = await axios.all(requests); + results = results.map((result, i) => ({ + title: repos[i].title, + markdown: Base64.decode(result.data.content), + })); + return results; + } catch (_err) { + return []; + } }; /** diff --git a/modules/home/tests/home.integration.tests.js b/modules/home/tests/home.integration.tests.js index f2b2fdd46..f438dfff3 100644 --- a/modules/home/tests/home.integration.tests.js +++ b/modules/home/tests/home.integration.tests.js @@ -91,20 +91,20 @@ describe('Home integration tests:', () => { } }); - test('should return 422 when GitHub API fails for releases', async () => { + test('should return empty releases gracefully when GitHub API fails', async () => { axios.get.mockRejectedValueOnce(new Error('GitHub API unavailable')); - const result = await agent.get('/api/home/releases').expect(422); - expect(result.body.type).toBe('error'); - expect(result.body.message).toBe('Unprocessable Entity'); - expect(result.body.description).toBe('GitHub API unavailable.'); + const result = await agent.get('/api/home/releases').expect(200); + expect(result.body.type).toBe('success'); + expect(result.body.message).toBe('releases'); + expect(result.body.data).toEqual([]); }); - test('should return 422 when GitHub API fails for changelogs', async () => { + test('should return empty changelogs gracefully when GitHub API fails', async () => { axios.get.mockRejectedValueOnce(new Error('GitHub API unavailable')); - const result = await agent.get('/api/home/changelogs').expect(422); - expect(result.body.type).toBe('error'); - expect(result.body.message).toBe('Unprocessable Entity'); - expect(result.body.description).toBe('GitHub API unavailable.'); + const result = await agent.get('/api/home/changelogs').expect(200); + expect(result.body.type).toBe('success'); + expect(result.body.message).toBe('changelogs'); + expect(result.body.data).toEqual([]); }); test('should use Authorization header when a token is configured for releases', async () => {