Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/install-dependencies/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ runs:
shell: bash

- name: Check dependency cache
uses: actions/cache@v4
uses: actions/cache@v5
id: cache_dependencies
with:
path: ${{ env.CACHED_DEPENDENCY_PATHS }}
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ local.log

.rpt2_cache

# verdaccio local registry (e2e tests)
dev-packages/e2e-tests/verdaccio-config/storage/
dev-packages/e2e-tests/verdaccio-config/.verdaccio.pid

lint-results.json
trace.zip

Expand Down
1 change: 0 additions & 1 deletion dev-packages/e2e-tests/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const TEST_REGISTRY_CONTAINER_NAME = 'verdaccio-e2e-test-registry';
export const DEFAULT_BUILD_TIMEOUT_SECONDS = 60 * 5;
export const DEFAULT_TEST_TIMEOUT_SECONDS = 60 * 2;
export const VERDACCIO_VERSION = '5.22.1';
33 changes: 22 additions & 11 deletions dev-packages/e2e-tests/lib/publishPackages.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,34 @@
/* eslint-disable no-console */
import * as childProcess from 'child_process';
import { spawn } from 'child_process';
import { readFileSync } from 'fs';
import { globSync } from 'glob';
import * as path from 'path';

const repositoryRoot = path.resolve(__dirname, '../../..');

function npmPublish(tarballPath: string, npmrc: string): Promise<void> {
return new Promise((resolve, reject) => {
const child = spawn('npm', ['--userconfig', npmrc, 'publish', tarballPath], {
cwd: repositoryRoot,
stdio: 'inherit',
});

child.on('error', reject);
child.on('close', code => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Error publishing tarball ${tarballPath}`));
}
});
});
}

/**
* Publishes all built Sentry package tarballs to the local Verdaccio test registry.
* Uses async `npm publish` so an in-process Verdaccio can still handle HTTP on the event loop.
*/
export function publishPackages(): void {
export async function publishPackages(): Promise<void> {
const version = (JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8')) as { version: string })
.version;

Expand All @@ -28,14 +47,6 @@ export function publishPackages(): void {

for (const tarballPath of packageTarballPaths) {
console.log(`Publishing tarball ${tarballPath} ...`);
const result = childProcess.spawnSync('npm', ['--userconfig', npmrc, 'publish', tarballPath], {
cwd: repositoryRoot,
encoding: 'utf8',
stdio: 'inherit',
});

if (result.status !== 0) {
throw new Error(`Error publishing tarball ${tarballPath}`);
}
await npmPublish(tarballPath, npmrc);
}
}
4 changes: 3 additions & 1 deletion dev-packages/e2e-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"test:validate-test-app-setups": "ts-node validate-test-app-setups.ts",
"test:prepare": "ts-node prepare.ts",
"test:validate": "run-s test:validate-configuration test:validate-test-app-setups",
"clean": "rimraf tmp node_modules && yarn clean:test-applications && yarn clean:pnpm",
"clean:verdaccio": "sh -c 'pkill -f verdaccio-runner.mjs 2>/dev/null || true'",
"clean": "yarn clean:verdaccio && rimraf tmp node_modules verdaccio-config/storage && yarn clean:test-applications && yarn clean:pnpm",
"ci:build-matrix": "ts-node ./lib/getTestMatrix.ts",
"ci:build-matrix-optional": "ts-node ./lib/getTestMatrix.ts --optional=true",
"ci:copy-to-temp": "ts-node ./ciCopyToTemp.ts",
Expand All @@ -28,6 +29,7 @@
"glob": "^13.0.6",
"rimraf": "^6.1.3",
"ts-node": "10.9.2",
"verdaccio": "6.5.0",
"yaml": "2.8.3"
},
"volta": {
Expand Down
17 changes: 8 additions & 9 deletions dev-packages/e2e-tests/prepare.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
/* eslint-disable no-console */
import * as dotenv from 'dotenv';
import { registrySetup } from './registrySetup';
import { registryRelease, registrySetup } from './registrySetup';

async function run(): Promise<void> {
// Load environment variables from .env file locally
dotenv.config();

try {
registrySetup();
} catch (error) {
console.error(error);
process.exit(1);
}
await registrySetup({ daemonize: true });
// Leave Verdaccio running for later CI steps (e.g. pnpm install). Detached stdio so this process can exit cleanly.
registryRelease();
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
run();
run().catch(error => {
console.error(error);
process.exit(1);
});
160 changes: 126 additions & 34 deletions dev-packages/e2e-tests/registrySetup.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,142 @@
/* eslint-disable no-console */
import * as childProcess from 'child_process';
import { TEST_REGISTRY_CONTAINER_NAME, VERDACCIO_VERSION } from './lib/constants';
import { spawn, spawnSync, type ChildProcess } from 'child_process';
import * as fs from 'fs';
import * as http from 'http';
import * as path from 'path';
import { publishPackages } from './lib/publishPackages';

// https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines
function groupCIOutput(groupTitle: string, fn: () => void): void {
const VERDACCIO_PORT = 4873;

let verdaccioChild: ChildProcess | undefined;

export interface RegistrySetupOptions {
/**
* When true, Verdaccio is spawned detached with stdio disconnected from the parent.
* Use for `prepare.ts` so `yarn test:prepare` can exit while the registry keeps running.
* (Inherited stdio otherwise keeps the parent process tied to the child on many systems.)
*/
daemonize?: boolean;
}

/** Stops any Verdaccio runner from a previous prepare/run so port 4873 is free. */
function killStrayVerdaccioRunner(): void {
spawnSync('pkill', ['-f', 'verdaccio-runner.mjs'], { stdio: 'ignore' });
}

async function groupCIOutput(groupTitle: string, fn: () => void | Promise<void>): Promise<void> {
if (process.env.CI) {
console.log(`::group::${groupTitle}`);
fn();
console.log('::endgroup::');
try {
await Promise.resolve(fn());
} finally {
console.log('::endgroup::');
}
} else {
fn();
await Promise.resolve(fn());
}
}

export function registrySetup(): void {
groupCIOutput('Test Registry Setup', () => {
// Stop test registry container (Verdaccio) if it was already running
childProcess.spawnSync('docker', ['stop', TEST_REGISTRY_CONTAINER_NAME], { stdio: 'ignore' });
console.log('Stopped previously running test registry');

// Start test registry (Verdaccio)
const startRegistryProcessResult = childProcess.spawnSync(
'docker',
[
'run',
'--detach',
'--rm',
'--name',
TEST_REGISTRY_CONTAINER_NAME,
'-p',
'4873:4873',
'-v',
`${__dirname}/verdaccio-config:/verdaccio/conf`,
`verdaccio/verdaccio:${VERDACCIO_VERSION}`,
],
{ encoding: 'utf8', stdio: 'inherit' },
);

if (startRegistryProcessResult.status !== 0) {
throw new Error('Start Registry Process failed.');
function waitUntilVerdaccioResponds(maxRetries: number = 60): Promise<void> {
const pingUrl = `http://127.0.0.1:${VERDACCIO_PORT}/-/ping`;

function tryOnce(): Promise<boolean> {
return new Promise(resolve => {
const req = http.get(pingUrl, res => {
res.resume();
resolve((res.statusCode ?? 0) > 0 && (res.statusCode ?? 500) < 500);
});
req.on('error', () => resolve(false));
req.setTimeout(2000, () => {
req.destroy();
resolve(false);
});
});
}

return (async () => {
for (let i = 0; i < maxRetries; i++) {
if (await tryOnce()) {
return;
}
await new Promise(r => setTimeout(r, 1000));
}
throw new Error('Verdaccio did not start in time.');
})();
}

function startVerdaccioChild(configPath: string, port: number, daemonize: boolean): ChildProcess {
const runnerPath = path.join(__dirname, 'verdaccio-runner.mjs');
const verbose = process.env.E2E_VERDACCIO_VERBOSE === '1';
return spawn(process.execPath, [runnerPath, configPath, String(port)], {
detached: daemonize,
stdio: daemonize && !verbose ? 'ignore' : 'inherit',
});
}

publishPackages();
async function stopVerdaccioChild(): Promise<void> {
const child = verdaccioChild;
verdaccioChild = undefined;
if (!child || child.killed) {
return;
}
child.kill('SIGTERM');
await new Promise<void>(resolve => {
child.once('exit', () => resolve());
setTimeout(resolve, 5000);
});
}

export async function registrySetup(options: RegistrySetupOptions = {}): Promise<void> {
const { daemonize = false } = options;
await groupCIOutput('Test Registry Setup', async () => {
killStrayVerdaccioRunner();

const configPath = path.join(__dirname, 'verdaccio-config', 'config.yaml');
const storagePath = path.join(__dirname, 'verdaccio-config', 'storage');

// Clear previous registry storage to ensure a fresh state
fs.rmSync(storagePath, { recursive: true, force: true });

// Verdaccio runs in a child process so tarball uploads are not starved by the
// same Node event loop as ts-node (in-process runServer + npm publish could hang).
console.log('Starting Verdaccio...');

verdaccioChild = startVerdaccioChild(configPath, VERDACCIO_PORT, daemonize);

try {
await waitUntilVerdaccioResponds(60);
console.log('Verdaccio is ready');

await publishPackages();
} catch (error) {
await stopVerdaccioChild();
throw error;
}
});

console.log('');
console.log('');
}

/**
* Detach the Verdaccio child so `ts-node` can exit while the registry keeps running
* (e.g. CI: `yarn test:prepare` then `pnpm test:build` in later steps).
*/
export function registryRelease(): void {
const child = verdaccioChild;
verdaccioChild = undefined;
if (child && !child.killed) {
child.unref();
}
}

export async function registryCleanup(): Promise<void> {
await stopVerdaccioChild();
killStrayVerdaccioRunner();
const pidFile = path.join(__dirname, 'verdaccio-config', '.verdaccio.pid');
try {
fs.unlinkSync(pidFile);
} catch {
// File may not exist
}
}
25 changes: 15 additions & 10 deletions dev-packages/e2e-tests/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { sync as globSync } from 'glob';
import { tmpdir } from 'os';
import { join, resolve } from 'path';
import { copyToTemp } from './lib/copyToTemp';
import { registrySetup } from './registrySetup';
import { registryCleanup, registrySetup } from './registrySetup';

interface SentryTestVariant {
'build-command': string;
Expand Down Expand Up @@ -184,14 +184,16 @@ async function run(): Promise<void> {
...envVarsToInject,
};

const skipRegistry = !!process.env.SKIP_REGISTRY;

try {
if (!skipRegistry) {
await registrySetup();
}

console.log('Cleaning test-applications...');
console.log('');

if (!process.env.SKIP_REGISTRY) {
registrySetup();
}

await asyncExec('pnpm clean:test-applications', { env, cwd: __dirname });
await asyncExec('pnpm cache delete "@sentry/*"', { env, cwd: __dirname });

Expand Down Expand Up @@ -247,11 +249,14 @@ async function run(): Promise<void> {
// clean up (although this is tmp, still nice to do)
await rm(tmpDirPath, { recursive: true });
}
} catch (error) {
console.error(error);
process.exit(1);
} finally {
if (!skipRegistry) {
await registryCleanup();
}
}
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
run();
run().catch(error => {
console.error(error);
process.exit(1);
});
6 changes: 3 additions & 3 deletions dev-packages/e2e-tests/verdaccio-config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
# https://github.com/verdaccio/verdaccio/tree/master/conf
#

# path to a directory with all packages
storage: /verdaccio/storage/data
# Repo-local storage (relative to this file). Absolute /verdaccio/... matches Docker-only templates and is not writable on typical dev machines.
storage: ./storage/data

# https://verdaccio.org/docs/configuration#authentication
auth:
htpasswd:
file: /verdaccio/storage/htpasswd
file: ./storage/htpasswd

# https://verdaccio.org/docs/configuration#uplinks
# a list of other known repositories we can talk to
Expand Down
24 changes: 24 additions & 0 deletions dev-packages/e2e-tests/verdaccio-runner.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* eslint-disable no-console */
import { createRequire } from 'node:module';

const require = createRequire(import.meta.url);
const { runServer } = require('verdaccio');

const configPath = process.argv[2];
const port = parseInt(process.argv[3], 10);

if (!configPath || !Number.isFinite(port)) {
console.error('verdaccio-runner: expected <configPath> <port> argv');
process.exit(1);
}

try {
const server = await runServer(configPath, { listenArg: String(port) });
await new Promise((resolve, reject) => {
server.once('error', reject);
server.listen(port, '127.0.0.1', () => resolve());
});
} catch (err) {
console.error(err);
process.exit(1);
}
Loading
Loading