diff --git a/e2e-tests/community-plugin-build-package.test.ts b/e2e-tests/community-plugin-build-package.test.ts index c5490d6..f494160 100644 --- a/e2e-tests/community-plugin-build-package.test.ts +++ b/e2e-tests/community-plugin-build-package.test.ts @@ -9,8 +9,18 @@ const exec = promisify(require('child_process').exec); const CONTAINER_TOOL = process.env.CONTAINER_TOOL || 'podman'; +const LOG_PREFIX = '[e2e]'; + +function log(msg: string): void { + console.log(`${LOG_PREFIX} ${msg}`); +} + +function logSection(title: string): void { + console.log(`${LOG_PREFIX} --- ${title} ---`); +} + async function downloadFile(url: string, file: string): Promise { - console.log(`Downloading file from ${url} to ${file}`); + log(`Downloading ${url} -> ${file}`); const response = await axios({ method: 'GET', url: url, @@ -31,23 +41,41 @@ async function runCommand( command: string, options: { cwd?: string } = {}, ): Promise<{ stdout: string; stderr: string }> { - console.log( - `Executing command: ${command}, in directory: ${options.cwd || process.cwd()}`, - ); + const cwd = options.cwd || process.cwd(); - const { err, stdout, stderr } = await exec(command, { - shell: true, - ...options, - }); - console.log(`Command output: ${stdout}`); - console.log(`Command error output: ${stderr}`); - if (err) { - console.error(`Error executing command: ${command}`); - console.error(stderr); - console.error(stdout); - throw err; + try { + const { stdout, stderr } = await exec(command, { + shell: true, + maxBuffer: 10 * 1024 * 1024, // 10MB buffer for large outputs + ...options, + }); + return { stdout, stderr }; + } catch (err: unknown) { + const e = err as { + code?: string | number; + signal?: string; + stdout?: string; + stderr?: string; + }; + const out = (e.stdout ?? '').trim() || '(empty)'; + const errOut = (e.stderr ?? '').trim() || '(empty)'; + const enrichedMessage = [ + `Command failed: ${command}`, + `Cwd: ${cwd}`, + `Exit code: ${e.code ?? 'N/A'} | Signal: ${e.signal ?? 'N/A'}`, + '--- stdout ---', + out, + '--- stderr ---', + errOut, + ].join('\n'); + + console.error(`${LOG_PREFIX} COMMAND FAILED: ${command}`); + console.error(`${LOG_PREFIX} cwd: ${cwd}`); + console.error(`${LOG_PREFIX} --- stdout ---\n${out}`); + console.error(`${LOG_PREFIX} --- stderr ---\n${errOut}`); + + throw new Error(enrichedMessage); } - return { stdout, stderr }; } async function parseDynamicPluginAnnotation( @@ -88,9 +116,10 @@ describe('export and package backstage-community plugin', () => { jest.setTimeout(TEST_TIMEOUT); beforeAll(async () => { - console.log(`Using rhdh-cli at: ${RHDH_CLI}`); - console.log(`Test workspace: ${tmpDir}`); - console.log(`Container tool: ${CONTAINER_TOOL}`); + logSection('Setup'); + log(`rhdh-cli: ${RHDH_CLI}`); + log(`workspace: ${tmpDir}`); + log(`container tool: ${CONTAINER_TOOL}`); let communityPluginsArchivePath = path.join( tmpDir, @@ -99,26 +128,20 @@ describe('export and package backstage-community plugin', () => { if (process.env.COMMUNITY_PLUGINS_REPO_ARCHIVE) { communityPluginsArchivePath = process.env.COMMUNITY_PLUGINS_REPO_ARCHIVE; - console.log( - `Using community plugins repo archive: ${communityPluginsArchivePath}`, - ); + log(`Community plugins: path from env: ${communityPluginsArchivePath}`); } - if (!fs.existsSync(communityPluginsArchivePath)) { - console.log(`Downloading community plugins archive from: ${REPO_URL}`); - await downloadFile(REPO_URL, communityPluginsArchivePath); - console.log( - `Downloaded community plugins archive to: ${communityPluginsArchivePath}`, + if (fs.existsSync(communityPluginsArchivePath)) { + log( + `Community plugins: using existing archive (skipping download): ${communityPluginsArchivePath}`, ); } else { - console.log( - `Using existing community plugins archive: ${communityPluginsArchivePath}`, - ); + log(`Community plugins: archive not found, downloading from ${REPO_URL}`); + await downloadFile(REPO_URL, communityPluginsArchivePath); + log(`Community plugins: downloaded to ${communityPluginsArchivePath}`); } - console.log( - `Extracting community plugins archive to: ${getClonedRepoPath()}`, - ); + log(`Community plugins: extracting to ${getClonedRepoPath()}`); fs.mkdirSync(getClonedRepoPath(), { recursive: true }); await tar.x({ file: communityPluginsArchivePath, @@ -136,29 +159,40 @@ describe('export and package backstage-community plugin', () => { describe.each([ [ - 'workspaces/tech-radar/plugins/tech-radar', + 'workspaces/tech-radar', + 'plugins/tech-radar', `rhdh-test-tech-radar-frontend:${Date.now()}`, ], [ - 'workspaces/tech-radar/plugins/tech-radar-backend', + 'workspaces/tech-radar', + 'plugins/tech-radar-backend', `rhdh-test-tech-radar-backend:${Date.now()}`, ], - ])('plugin in %s directory', (pluginPath, imageTag) => { - const getFullPluginPath = () => path.join(getClonedRepoPath(), pluginPath); + ])('plugin in %s/%s directory', (workspacePath, pluginRelPath, imageTag) => { + const getWorkspacePath = () => + path.join(getClonedRepoPath(), workspacePath); + const getFullPluginPath = () => + path.join(getClonedRepoPath(), workspacePath, pluginRelPath); beforeAll(async () => { - console.log(`Installing dependencies in ${getFullPluginPath()}`); - await runCommand(`yarn install`, { - cwd: getFullPluginPath(), + logSection(`Plugin: ${workspacePath}/${pluginRelPath}`); + log(`Installing dependencies in workspace ${getWorkspacePath()}`); + // Use YARN_ENABLE_SCRIPTS=false to skip native module builds that may fail + await runCommand(`YARN_ENABLE_SCRIPTS=false yarn install`, { + cwd: getWorkspacePath(), }); - console.log(`Compiling TypeScript in ${getFullPluginPath()}`); - await runCommand(`npx tsc`, { + log(`Generating TypeScript declarations in ${getWorkspacePath()}`); + await runCommand(`yarn tsc`, { + cwd: getWorkspacePath(), + }); + log(`Building plugin in ${getFullPluginPath()}`); + await runCommand(`yarn build`, { cwd: getFullPluginPath(), }); }); afterAll(async () => { - console.log(`Cleaning up image: ${imageTag}`); + log(`Cleaning up image: ${imageTag}`); await runCommand(`${CONTAINER_TOOL} rmi -f ${imageTag}`); }); @@ -198,10 +232,8 @@ describe('export and package backstage-community plugin', () => { ); const imageMetadata = await getImageMetadata(imageTag); - console.log( - `Image annotations: ${JSON.stringify(imageMetadata.annotations)}`, - ); - console.log(`Image labels: ${JSON.stringify(imageMetadata.labels)}`); + log(`Image annotations: ${JSON.stringify(imageMetadata.annotations)}`); + log(`Image labels: ${JSON.stringify(imageMetadata.labels)}`); // There needs to be at least one annotation (the default dynamic plugin annotation) expect(imageMetadata.annotations).not.toBeNull(); diff --git a/e2e-tests/jest.config.js b/e2e-tests/jest.config.js index 7ea56d0..c4d1dd7 100644 --- a/e2e-tests/jest.config.js +++ b/e2e-tests/jest.config.js @@ -5,6 +5,7 @@ const tsJestTransformCfg = createDefaultPreset().transform; /** @type {import("jest").Config} **/ module.exports = { testEnvironment: 'node', + setupFilesAfterEnv: ['/setup.js'], transform: { ...tsJestTransformCfg, }, diff --git a/e2e-tests/setup.js b/e2e-tests/setup.js new file mode 100644 index 0000000..e8fc715 --- /dev/null +++ b/e2e-tests/setup.js @@ -0,0 +1,7 @@ +// Plain console output for e2e tests (no Jest "console.log" label or stack trace) +console.log = (...args) => { + process.stdout.write(args.map(String).join(' ') + '\n'); +}; +console.error = (...args) => { + process.stderr.write(args.map(String).join(' ') + '\n'); +};