Skip to content

Commit 8956b13

Browse files
committed
Test monorepo handlers and fix errors
1 parent dd4f2e1 commit 8956b13

12 files changed

Lines changed: 48202 additions & 31165 deletions

File tree

__tests__/monorepo.test.ts

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
import * as actions_exec from '@actions/exec'
2+
import { vol } from 'memfs'
3+
import type { PackageJson } from 'type-fest'
4+
import type { ActionInputs } from '../src/inputs'
5+
import { listMonorepoProjects, type ProjectConfig } from '../src/monorepo'
6+
7+
jest.mock('fs', () => {
8+
const memfs: typeof import('memfs') = jest.requireActual('memfs')
9+
return memfs.fs
10+
})
11+
jest.mock('fs/promises', () => {
12+
const memfs: typeof import('memfs') = jest.requireActual('memfs')
13+
return memfs.fs.promises
14+
})
15+
jest.mock('node:fs', () => {
16+
const memfs: typeof import('memfs') = jest.requireActual('memfs')
17+
return memfs.fs
18+
})
19+
jest.mock('node:fs/promises', () => {
20+
const memfs: typeof import('memfs') = jest.requireActual('memfs')
21+
return memfs.fs.promises
22+
})
23+
24+
const MEMFS_VOLUME = '/test'
25+
26+
describe('monorepo projects detection', () => {
27+
const DEFAULT_INPUTS = {
28+
monorepo: true,
29+
projects: null,
30+
task: 'code-pushup',
31+
silent: false,
32+
directory: MEMFS_VOLUME,
33+
bin: 'npx --no-install code-pushup'
34+
} satisfies Partial<ActionInputs>
35+
36+
const pkgJsonContent = (content: PackageJson): string =>
37+
JSON.stringify(content)
38+
39+
beforeEach(() => {
40+
vol.reset()
41+
})
42+
43+
it('should detect projects in Nx monorepo', async () => {
44+
jest.spyOn(actions_exec, 'exec').mockResolvedValue(0)
45+
jest.spyOn(actions_exec, 'getExecOutput').mockResolvedValue({
46+
exitCode: 0,
47+
stdout: '["backend", "frontend"]',
48+
stderr: ''
49+
})
50+
51+
vol.fromJSON(
52+
{
53+
'nx.json': '{}',
54+
'backend/project.json': JSON.stringify({
55+
name: 'backend',
56+
targets: { 'code-pushup': { executor: '@code-pushup/nx-plugin:cli' } }
57+
}),
58+
'frontend/project.json': JSON.stringify({
59+
name: 'frontend',
60+
targets: { 'code-pushup': { executor: '@code-pushup/nx-plugin:cli' } }
61+
})
62+
},
63+
MEMFS_VOLUME
64+
)
65+
66+
await expect(listMonorepoProjects(DEFAULT_INPUTS)).resolves.toEqual<
67+
ProjectConfig[]
68+
>([
69+
{ name: 'backend', bin: 'npx nx run backend:code-pushup' },
70+
{ name: 'frontend', bin: 'npx nx run frontend:code-pushup' }
71+
])
72+
})
73+
74+
it('should detect projects in Turborepo', async () => {
75+
vol.fromJSON(
76+
{
77+
'package.json': pkgJsonContent({
78+
private: true,
79+
workspaces: ['frontend/*', 'backend/*'],
80+
devDependencies: { '@code-pushup/cli': 'latest' }
81+
}),
82+
'yarn.lock': '',
83+
'turbo.json': JSON.stringify({
84+
tasks: {
85+
'code-pushup': {
86+
env: ['CP_API_KEY'],
87+
outputs: ['.code-pushup']
88+
}
89+
}
90+
}),
91+
'backend/api/package.json': pkgJsonContent({
92+
name: 'api',
93+
scripts: { 'code-pushup': 'code-pushup --no-progress' }
94+
}),
95+
'backend/auth/package.json': pkgJsonContent({
96+
name: 'auth',
97+
scripts: { 'code-pushup': 'code-pushup --no-progress' }
98+
}),
99+
'frontend/backoffice/package.json': pkgJsonContent({
100+
name: 'backoffice',
101+
scripts: { 'code-pushup': 'code-pushup --no-progress' }
102+
}),
103+
'frontend/website/package.json': pkgJsonContent({
104+
name: 'website',
105+
scripts: { 'code-pushup': 'code-pushup --no-progress' }
106+
})
107+
},
108+
MEMFS_VOLUME
109+
)
110+
111+
await expect(listMonorepoProjects(DEFAULT_INPUTS)).resolves.toEqual<
112+
ProjectConfig[]
113+
>([
114+
{ name: 'backoffice', bin: 'npx turbo run code-pushup -F backoffice' },
115+
{ name: 'website', bin: 'npx turbo run code-pushup -F website' },
116+
{ name: 'api', bin: 'npx turbo run code-pushup -F api' },
117+
{ name: 'auth', bin: 'npx turbo run code-pushup -F auth' }
118+
])
119+
})
120+
121+
it('should detect packages in PNPM workspace with code-pushup script', async () => {
122+
vol.fromJSON(
123+
{
124+
'package.json': pkgJsonContent({}),
125+
'pnpm-workspace.yaml': 'packages:\n- apps/*\n- libs/*\n\n',
126+
'apps/backend/package.json': pkgJsonContent({
127+
name: 'backend',
128+
scripts: { 'code-pushup': 'code-pushup' }
129+
}),
130+
'apps/frontend/package.json': pkgJsonContent({
131+
name: 'frontend',
132+
scripts: { 'code-pushup': 'code-pushup' }
133+
}),
134+
'libs/eslint-config/package.json': pkgJsonContent({
135+
name: '@repo/eslint-config'
136+
}),
137+
'libs/utils/package.json': pkgJsonContent({
138+
name: '@repo/utils',
139+
scripts: { 'code-pushup': 'code-pushup' }
140+
})
141+
},
142+
MEMFS_VOLUME
143+
)
144+
145+
await expect(listMonorepoProjects(DEFAULT_INPUTS)).resolves.toEqual<
146+
ProjectConfig[]
147+
>([
148+
{ name: 'backend', bin: 'pnpm run code-pushup -F backend' },
149+
{ name: 'frontend', bin: 'pnpm run code-pushup -F frontend' },
150+
{ name: '@repo/utils', bin: 'pnpm run code-pushup -F @repo/utils' }
151+
])
152+
})
153+
154+
it('should detect Yarn workspaces with code-pushup installed individually', async () => {
155+
vol.fromJSON(
156+
{
157+
'package.json': pkgJsonContent({
158+
private: true,
159+
workspaces: ['packages/*']
160+
}),
161+
'yarn.lock': '',
162+
'packages/cli/package.json': pkgJsonContent({
163+
name: 'cli',
164+
devDependencies: { '@code-pushup/cli': '^0.42.0' }
165+
}),
166+
'packages/core/package.json': pkgJsonContent({
167+
name: 'core',
168+
devDependencies: { '@code-pushup/cli': '^0.42.0' }
169+
}),
170+
'e2e/package.json': pkgJsonContent({
171+
name: 'e2e-tests'
172+
})
173+
},
174+
MEMFS_VOLUME
175+
)
176+
177+
await expect(listMonorepoProjects(DEFAULT_INPUTS)).resolves.toEqual<
178+
ProjectConfig[]
179+
>([
180+
{ name: 'cli', bin: 'yarn workspace cli exec code-pushup' },
181+
{ name: 'core', bin: 'yarn workspace core exec code-pushup' }
182+
])
183+
})
184+
185+
it('should detect NPM workspaces when code-pushup installed at root level', async () => {
186+
vol.fromJSON(
187+
{
188+
'package.json': pkgJsonContent({
189+
private: true,
190+
workspaces: ['packages/*'],
191+
devDependencies: { '@code-pushup/cli': '^0.42.0' }
192+
}),
193+
'package-lock.json': '',
194+
'packages/backend/package.json': pkgJsonContent({
195+
name: 'backend'
196+
}),
197+
'packages/frontend/package.json': pkgJsonContent({
198+
name: 'frontend'
199+
})
200+
},
201+
MEMFS_VOLUME
202+
)
203+
204+
await expect(listMonorepoProjects(DEFAULT_INPUTS)).resolves.toEqual<
205+
ProjectConfig[]
206+
>([
207+
{ name: 'backend', bin: 'npm -w backend exec code-pushup' },
208+
{ name: 'frontend', bin: 'npm -w frontend exec code-pushup' }
209+
])
210+
})
211+
212+
it('should list folders matching globs passed as input when no tool detected', async () => {
213+
vol.fromJSON(
214+
{
215+
'frontend/package.json': pkgJsonContent({
216+
name: 'frontend',
217+
devDependencies: { '@code-pushup/cli': '^0.42.0' }
218+
}),
219+
'backend/auth/package.json': pkgJsonContent({
220+
name: 'auth',
221+
devDependencies: { '@code-pushup/cli': '^0.42.0' }
222+
}),
223+
'backend/api/package.json': pkgJsonContent({
224+
name: 'api',
225+
devDependencies: { '@code-pushup/cli': '^0.42.0' }
226+
})
227+
},
228+
MEMFS_VOLUME
229+
)
230+
231+
await expect(
232+
listMonorepoProjects({
233+
...DEFAULT_INPUTS,
234+
monorepo: true,
235+
projects: ['backend/*', 'frontend']
236+
})
237+
).resolves.toEqual<ProjectConfig[]>([
238+
{
239+
name: 'frontend',
240+
bin: 'npx --no-install code-pushup',
241+
directory: `${MEMFS_VOLUME}/frontend`
242+
},
243+
{
244+
name: 'backend/api',
245+
bin: 'npx --no-install code-pushup',
246+
directory: `${MEMFS_VOLUME}/backend/api`
247+
},
248+
{
249+
name: 'backend/auth',
250+
bin: 'npx --no-install code-pushup',
251+
directory: `${MEMFS_VOLUME}/backend/auth`
252+
}
253+
])
254+
})
255+
256+
it('should list all folders with a package.json when no tool detected and no patterns provided', async () => {
257+
vol.fromJSON(
258+
{
259+
'package.json': pkgJsonContent({
260+
name: 'my-app',
261+
devDependencies: { '@code-pushup/cli': '^0.42.0' }
262+
}),
263+
'scripts/generate-token/package.json': pkgJsonContent({}),
264+
'scripts/db/migrate/package.json': pkgJsonContent({}),
265+
'scripts/db/seed/package.json': pkgJsonContent({})
266+
},
267+
MEMFS_VOLUME
268+
)
269+
270+
await expect(
271+
listMonorepoProjects({
272+
...DEFAULT_INPUTS,
273+
monorepo: true,
274+
projects: null
275+
})
276+
).resolves.toEqual<ProjectConfig[]>([
277+
{
278+
name: 'my-app',
279+
bin: 'npx --no-install code-pushup',
280+
directory: MEMFS_VOLUME
281+
},
282+
{
283+
name: 'generate-token',
284+
bin: 'npx --no-install code-pushup',
285+
directory: `${MEMFS_VOLUME}/scripts/generate-token`
286+
},
287+
{
288+
name: 'migrate',
289+
bin: 'npx --no-install code-pushup',
290+
directory: `${MEMFS_VOLUME}/scripts/db/migrate`
291+
},
292+
{
293+
name: 'seed',
294+
bin: 'npx --no-install code-pushup',
295+
directory: `${MEMFS_VOLUME}/scripts/db/seed`
296+
}
297+
])
298+
})
299+
300+
it('should prefer tool provided as input (PNPM) over tool which would be auto-detected otherwise (Turborepo)', async () => {
301+
vol.fromJSON(
302+
{
303+
'package.json': pkgJsonContent({
304+
devDependencies: { '@code-pushup/cli': '^0.42.0' }
305+
}),
306+
'pnpm-workspace.yaml': 'packages:\n- apps/*\n- packages/*\n\n',
307+
'turbo.json': JSON.stringify({
308+
tasks: {
309+
'code-pushup': {}
310+
}
311+
}),
312+
'apps/backoffice/package.json': pkgJsonContent({
313+
name: 'backoffice'
314+
}),
315+
'apps/frontoffice/package.json': pkgJsonContent({
316+
name: 'frontoffice'
317+
}),
318+
'packages/models/package.json': pkgJsonContent({
319+
name: '@repo/models'
320+
}),
321+
'packages/ui/package.json': pkgJsonContent({
322+
name: '@repo/ui'
323+
})
324+
},
325+
MEMFS_VOLUME
326+
)
327+
328+
await expect(
329+
listMonorepoProjects({ ...DEFAULT_INPUTS, monorepo: 'pnpm' })
330+
).resolves.toEqual<ProjectConfig[]>([
331+
{ name: 'backoffice', bin: 'pnpm -F backoffice exec code-pushup' },
332+
{ name: 'frontoffice', bin: 'pnpm -F frontoffice exec code-pushup' },
333+
{ name: '@repo/models', bin: 'pnpm -F @repo/models exec code-pushup' },
334+
{ name: '@repo/ui', bin: 'pnpm -F @repo/ui exec code-pushup' }
335+
])
336+
})
337+
})

0 commit comments

Comments
 (0)