Skip to content

Commit e0d7e7f

Browse files
authored
refactor: local registry (#798)
1 parent f8f6b74 commit e0d7e7f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1904
-373
lines changed

docs/e2e.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# End-to-end testing
2+
3+
## Publishing (only used for E2E tests with local registry)
4+
5+
> [!NOTE] Projects are marked as publishabel by adding a target named `publishabel`.
6+
> Those libraries will have dynamic targets to publish and install the package.
7+
8+
Every publishable project in the monorepo has the following targets:
9+
10+
- [`publish`](./tools/src/publish/README.md#publish) - publish the package to the local registry
11+
- [`npm-check`](./tools/src/npm/README.md#npm-check) - check if the package is installed in registry
12+
- [`npm-install`](./tools/src/npm/README.md#npm-install) - install package in workspace.
13+
- [`npm-uninstall`](./tools/src/npm/README.md#npm-uninstall) - uninstall package form workspace
14+
15+
The following steps are necessary to publish a package:
16+
17+
1. `nx run <project-name>:npm-check` - check if the package is not already published
18+
2. `nx run <project-name>:publish --nextVersion=<version>` - publish package (login required)
19+
3. `nx run <project-name>:npm-check` - check if the package is published
20+
21+
## E2E testing
22+
23+
> [!NOTE] Projects that need verdaccio are identified over the `e2e` target.
24+
> Those libraries will have dynamic targets to start verdaccio and test the package.
25+
26+
All E2E tests use verdaccio to test the build artefact in a real registry.
27+
28+
Every E2E project in the monorepo has the following targets:
29+
30+
- [`start-verdaccio`](./tools/src/verdaccio/README.md#start-verdaccio) - start a local registry
31+
32+
#### Running E2E tests for a given project:
33+
34+
Every project in the monorepo that has E2E tests follows the project naming pattern: `<project-name>-e2e`.
35+
36+
Examples:
37+
38+
- `npx nx e2e cli-e2e` - run E2E tests for the cli project
39+
- `npx nx e2e cli-e2e --skipNxCache` - pass Nx CLI arguments
40+
- `npx nx run-many -t e2e` - run all E2E tests
41+
42+
### E2E testing process
43+
44+
The `e2e` testing process is complex and involves multiple steps and targets.
45+
46+
#### Overview:
47+
48+
- `nx run e2e <project-name>`
49+
- `global-setup.e2e.ts#setup` (vitest setup script configured in `vite.config.e2e.ts`)
50+
- setup - `nx start-verdaccio`
51+
- setup - `nx run-many -t publish`
52+
- setup - `nx run-many -t npm-install`
53+
- **run tests**
54+
- `global-setup.e2e.ts#teardown` (vitest teardown script configured in `vite.config.e2e.ts`)
55+
- teardown - `nx run-many -t npm-uninstall`
56+
- teardown - `process.kill(<verdaccio-port>)`
57+
58+
```mermaid
59+
graph TD
60+
A["nx run e2e <project-name>"] --> B[global-setup.e2e.ts]
61+
B --> C[nx start-verdaccio]
62+
C --> D[nx run-many -t publish]
63+
D --> E[nx run-many -t npm-install]
64+
E --> F[vitest test]
65+
F --> G[nx run-many -t npm-uninstall]
66+
G --> H["process.kill(<verdaccio-port>)"]
67+
```
68+
69+
#### Involved files:
70+
71+
```sh
72+
Root/
73+
├── e2e/
74+
│ └── <project-name>-e2e/
75+
│ ├── tests/
76+
│ │ └── <file-name>.e2e.ts
77+
│ ├── vite.config.e2e.ts # uses `global-setup.e2e.ts` for as `globalSetup` script
78+
│ └── project.json
79+
├── packages/
80+
│ └── <project-name>/
81+
│ └── project.json # marked as "publishable"
82+
├── .verdaccio/
83+
│ └── config.yaml
84+
├── tools/ # all plugins registered in nx.json
85+
│ └── src/
86+
│ ├── npm/
87+
│ │ └── npm.plugin.ts
88+
│ ├── publish/
89+
│ │ └── publish.plugin.ts
90+
│ └── verdaccio/
91+
│ └── verdaccio.plugin.ts
92+
├── global-setup.e2e.ts
93+
└── nx.json # registers npm, publish and verdaccio plugin
94+
```
95+
96+
#### `nx e2e <project-name>` process:
97+
98+
1. Nx derives all dynamic targets from the plugins registered in `nx.json`.
99+
The following plugins are registered to publish packages to a local registry:
100+
101+
```jsonc
102+
// nx.json
103+
{
104+
// ...
105+
"plugins": ["tools/src/npm/npm.plugin.ts", "tools/src/publish/publish.plugin.ts", "tools/src/verdaccio/verdaccio.plugin.ts"]
106+
}
107+
```
108+
109+
2. The `e2e` target registered in `<project-name>/project.json` is executed:
110+
111+
```jsonc
112+
{
113+
"targets": {
114+
// ...
115+
"e2e": {
116+
"executor": "@nx/vite:test",
117+
"options": {
118+
"configFile": "e2e/<project-name>-e2e/vite.config.e2e.ts"
119+
}
120+
}
121+
}
122+
}
123+
```
124+
125+
2. 1. The `vite.config.e2e.ts` file is used to configure the Vite test runner.
126+
Inside the file, the `globalSetup` option is set to `global-setup.e2e.ts`:
127+
128+
```typescript
129+
export default defineConfig({
130+
// ...
131+
globalSetup: ['../../global-setup.e2e.ts'],
132+
});
133+
```
134+
135+
2. 2. The `global-setup.e2e.ts` file is used to configure the global `setup` scripts for the test runner:
136+
The `setup` function is executed before the tests are run.
137+
It starts a local registry, publishes the package, and installs it in the project.
138+
139+
> [!NOTE]
140+
> ATM the E2E tests install the packages in the workspace root which blocks the parallel execution of E2E tests.
141+
142+
```typescript
143+
// global-setup.e2e.ts
144+
145+
// to avoid port conflicts ever E2E targets has a unique port
146+
const uniquePort = 6000 + Math.round(Math.random() * 1000);
147+
const e2eDir = join('tmp', 'e2e');
148+
149+
export async function setup() {
150+
// start local verdaccio registry
151+
const { registry } = await startLocalRegistry({
152+
localRegistryTarget: '@code-pushup/cli-source:start-verdaccio',
153+
// to avoid file system conflicts ever E2E targets has a unique storage folder
154+
storage: join(join(e2eDir, `registry-${uniquePort}`), 'storage'),
155+
port: uniquePort,
156+
});
157+
158+
// package publish & install
159+
const version = findLatestVersion();
160+
nxRunManyPublish({ registry, nextVersion: version });
161+
nxRunManyNpmInstall({ registry, pkgVersion: version });
162+
}
163+
```
164+
165+
3. The tests are executed and import the packages over their package name not over the build output.
166+
167+
```typescript
168+
// correct
169+
import * as packageName from "<package-name>"
170+
// wrong
171+
import * as packageName from "./dist/packages/<project-name>/src/index.js"
172+
```
173+
174+
4. The `global-setup.e2e.ts` file is used to configure the global `teardown` scripts for the test runner:
175+
The `teardown` function is executed after the tests are run.
176+
It uninstalls the package and stops the local registry.
177+
178+
```typescript
179+
// global-setup.e2e.ts
180+
181+
export async function teardown() {
182+
// package uninstall
183+
nxRunManyNpmUninstall();
184+
// stop local verdaccio registry
185+
stopLocalRegistry({ port: uniquePort });
186+
}
187+
```
188+
189+
#### Changes/generated files during e2e tests:
190+
191+
```sh
192+
Root/
193+
├── dist/
194+
│ └── packages/
195+
│ └── <project-name>/...
196+
├── e2e/
197+
│ └── <project-name>-e2e/
198+
│ └── __snapshots__/...
199+
├── tmp/
200+
│ └── e2e/
201+
│ └── registry-<port>/
202+
│ ├── storage/...
203+
│ ├── node_modules/...
204+
│ └── <test-name>/...
205+
│ └── src/...
206+
├── package-lock.json # npm install/uninstall installs into workspace root
207+
└── package.json # npm install/uninstall installs into workspace root
208+
```
209+
210+
After running the E2E tests all changes are reverted, and the workspace is in the same state as before the tests.
211+
212+
### E2E testing troubleshooting
213+
214+
- start local registry manually with `nx start-verdaccio` - logs port
215+
- check if a package is published with `nx npm-check <project-name> --registry=http://localhost:<port>`
216+
- install a package in workspace `nx npm-install <project-name> --registry=http://localhost:<port>`
217+
- uninstall a package from workspace `nx npm-uninstall <project-name>`

e2e/cli-e2e/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "cli-e2e",
33
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4-
"sourceRoot": "examples/cli-e2e/src",
4+
"sourceRoot": "e2e/cli-e2e/src",
55
"projectType": "application",
66
"targets": {
77
"lint": {

e2e/create-cli-e2e/tests/init.e2e.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ describe('create-cli-node', () => {
4040
command: 'npm',
4141
args: ['exec', '@code-pushup/create-cli'],
4242
cwd,
43-
observer: { onStdout: console.info },
4443
});
4544

4645
expect(code).toBe(0);
@@ -57,7 +56,6 @@ describe('create-cli-node', () => {
5756
command: 'npm',
5857
args: ['init', '@code-pushup/cli'],
5958
cwd,
60-
observer: { onStdout: console.info },
6159
});
6260

6361
expect(code).toBe(0);

e2e/nx-plugin-e2e/project.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "nx-plugin-e2e",
33
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4-
"sourceRoot": "examples/nx-plugin-e2e/src",
4+
"sourceRoot": "e2e/nx-plugin-e2e/src",
55
"projectType": "application",
66
"targets": {
77
"lint": {

e2e/nx-plugin-e2e/tests/generator-init.e2e.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('nx-plugin g init', () => {
2929
await rm(baseDir, { recursive: true, force: true });
3030
});
3131

32-
it('should inform about dry run', async () => {
32+
it('should inform about dry run when used on init generator', async () => {
3333
const cwd = join(baseDir, 'dry-run');
3434
await materializeTree(tree, cwd);
3535

global-setup.e2e.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,62 @@
1-
import { execFileSync, execSync } from 'child_process';
1+
import { join } from 'node:path';
22
import { setup as globalSetup } from './global-setup';
33
import { setupTestFolder, teardownTestFolder } from './testing/test-setup/src';
4-
import startLocalRegistry from './tools/scripts/start-local-registry';
5-
import stopLocalRegistry from './tools/scripts/stop-local-registry';
4+
import {
5+
nxRunManyNpmInstall,
6+
nxRunManyNpmUninstall,
7+
} from './tools/src/npm/utils';
8+
import { findLatestVersion, nxRunManyPublish } from './tools/src/publish/utils';
9+
import startLocalRegistry from './tools/src/verdaccio/start-local-registry';
10+
import stopLocalRegistry from './tools/src/verdaccio/stop-local-registry';
11+
import { RegistryResult } from './tools/src/verdaccio/types';
12+
import { uniquePort } from './tools/src/verdaccio/utils';
613

7-
const localRegistryNxTarget = '@code-pushup/cli-source:local-registry';
14+
const e2eDir = join('tmp', 'e2e');
15+
const uniqueDir = join(e2eDir, `registry-${uniquePort()}`);
16+
17+
let activeRegistry: RegistryResult;
818

919
export async function setup() {
1020
await globalSetup();
11-
await setupTestFolder('tmp/local-registry');
12-
await setupTestFolder('tmp/e2e');
21+
await setupTestFolder(e2eDir);
1322

1423
try {
15-
await startLocalRegistry({ localRegistryTarget: localRegistryNxTarget });
24+
activeRegistry = await startLocalRegistry({
25+
localRegistryTarget: '@code-pushup/cli-source:start-verdaccio',
26+
storage: join(uniqueDir, 'storage'),
27+
port: uniquePort(),
28+
});
1629
} catch (error) {
1730
console.error('Error starting local verdaccio registry:\n' + error.message);
1831
throw error;
1932
}
2033

34+
// package publish
35+
const { registry } = activeRegistry.registryData;
36+
try {
37+
console.info('Publish packages');
38+
nxRunManyPublish({ registry, nextVersion: findLatestVersion() });
39+
} catch (error) {
40+
console.error('Error publishing packages:\n' + error.message);
41+
throw error;
42+
}
43+
44+
// package install
2145
try {
2246
console.info('Installing packages');
23-
execFileSync(
24-
'npx',
25-
['nx', 'run-many', '--targets=npm-install', '--parallel=1'],
26-
{ env: process.env, stdio: 'inherit', shell: true },
27-
);
47+
nxRunManyNpmInstall({ registry });
2848
} catch (error) {
2949
console.error('Error installing packages:\n' + error.message);
3050
throw error;
3151
}
3252
}
3353

3454
export async function teardown() {
35-
stopLocalRegistry();
36-
console.info('Uninstalling packages');
37-
execFileSync(
38-
'npx',
39-
['nx', 'run-many', '--targets=npm-uninstall', '--parallel=1'],
40-
{ env: process.env, stdio: 'inherit', shell: true },
41-
);
42-
await teardownTestFolder('tmp/e2e');
43-
await teardownTestFolder('tmp/local-registry');
55+
if (activeRegistry && 'registryData' in activeRegistry) {
56+
const { stop } = activeRegistry;
57+
58+
stopLocalRegistry(stop);
59+
nxRunManyNpmUninstall();
60+
}
61+
await teardownTestFolder(e2eDir);
4462
}

nx.json

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,18 @@
110110
},
111111
"releaseTagPattern": "v{version}"
112112
},
113-
"plugins": ["./tools/scripts/publish.plugin.ts"]
113+
"plugins": [
114+
{
115+
"plugin": "./tools/src/npm/npm.plugin.ts",
116+
"options": { "verbose": true }
117+
},
118+
{
119+
"plugin": "./tools/src/publish/publish.plugin.ts",
120+
"options": { "verbose": true }
121+
},
122+
{
123+
"plugin": "./tools/src/verdaccio/verdaccio.plugin.ts",
124+
"options": { "verbose": true }
125+
}
126+
]
114127
}

0 commit comments

Comments
 (0)