name: Test App on: merge_group: workflow_dispatch: push: branches: - develop pull_request: types: - opened - synchronize concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Test: timeout-minutes: 20 runs-on: ubuntu-24.04 permissions: contents: read pull-requests: write packages: read steps: - name: Checkout branch uses: actions/checkout@v6 - name: Setup Node uses: actions/setup-node@v6 with: node-version-file: .nvmrc cache: npm cache-dependency-path: package-lock.json registry-url: 'https://npm.pkg.github.com' scope: '@kong' - name: Install packages run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' - name: Lint run: npm run lint - name: Type checks run: npm run type-check - name: Unit Tests run: npm test - name: Checkout base branch (cycle comparison) if: github.event_name == 'pull_request' && always() uses: actions/checkout@v6 with: ref: ${{ github.event.pull_request.base.ref }} path: insomnia-base - name: Setup Node (base branch tree) if: github.event_name == 'pull_request' && always() uses: actions/setup-node@v6 with: node-version-file: insomnia-base/.nvmrc cache: npm cache-dependency-path: insomnia-base/package-lock.json registry-url: 'https://npm.pkg.github.com' scope: '@kong' - name: Install packages (base branch tree) if: github.event_name == 'pull_request' && always() working-directory: insomnia-base run: npm ci env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' - name: Check Circular References if: github.event_name == 'pull_request' && always() uses: actions/github-script@v7 env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const path = require('path'); const workspaceRoot = process.env.GITHUB_WORKSPACE; const prPackagesDir = path.join(workspaceRoot, 'packages'); const basePackagesDir = path.join(workspaceRoot, 'insomnia-base', 'packages'); async function analyzeCircularReferences(rootDir) { const madge = require('madge'); const res = await madge(rootDir, { fileExtensions: ['ts', 'tsx'], }); return res.circular(); } // Function to generate markdown report function generateMarkdownReport(prCircularRefs, baseCircularRefs, prCount, baseCount, baseBranch) { const timestamp = new Date().toISOString(); const diff = prCount - baseCount; const diffPercent = baseCount > 0 ? ((diff / baseCount) * 100).toFixed(2) : '0.00'; let status = '✅ PASSED'; let statusEmoji = '✅'; if (diff > 0) { status = '⚠️ WARNING'; statusEmoji = '⚠️'; } else if (diff === 0) { status = '✅ NO CHANGE'; statusEmoji = '✅'; } else { status = '✨ IMPROVED'; statusEmoji = '✨'; } // Find new and removed circular references const prCycleStrings = new Set(prCircularRefs.map(cycle => cycle.join(' -> '))); const baseCycleStrings = new Set(baseCircularRefs.map(cycle => cycle.join(' -> '))); const newCycles = Array.from(prCycleStrings).filter(cycle => !baseCycleStrings.has(cycle)).sort(); const removedCycles = Array.from(baseCycleStrings).filter(cycle => !prCycleStrings.has(cycle)).sort(); return `# ${statusEmoji} Circular References Report **Generated at:** ${timestamp} **Status:** ${status} ## Summary | Metric | Base (\`${baseBranch}\`) | PR | Change | |--------|----------------|-----|---------| | Total Circular References | ${baseCount} | ${prCount} | ${diff > 0 ? '+' : ''}${diff} (${diffPercent > 0 ? '+' : ''}${diffPercent}%) | ${newCycles.length > 0 ? ` ## ⚠️ New Circular References Added (${newCycles.length})
Click to expand/collapse \`\`\` ${newCycles.join('\n')} \`\`\`
` : ''} ${removedCycles.length > 0 ? ` ## ✨ Circular References Removed (${removedCycles.length})
Click to expand/collapse \`\`\` ${removedCycles.join('\n')} \`\`\`
` : ''}
Click to view all circular references in PR (${prCount}) \`\`\` ${prCircularRefs.length > 0 ? prCircularRefs.sort().map(cycle => cycle.join(' -> ')).join('\n') : 'No circular references found'} \`\`\`
Click to view all circular references in base branch (${baseCount}) \`\`\` ${baseCircularRefs.length > 0 ? baseCircularRefs.sort().map(cycle => cycle.join(' -> ')).join('\n') : 'No circular references found'} \`\`\`
## Analysis ${ diff > 0 ? `⚠️ **Warning:** This PR introduces ${diff} new circular ${diff === 1 ? 'reference' : 'references'}. Consider refactoring to avoid adding circular dependencies.` : diff < 0 ? `✨ **Great Job!** This PR removes ${Math.abs(diff)} circular ${Math.abs(diff) === 1 ? 'reference' : 'references'}. Keep up the good work!` : `✅ **No Change:** This PR does not introduce or remove any circular references.` } --- *This report was generated automatically by comparing against the \`${baseBranch}\` branch.* `; } try { const baseBranch = context.payload.pull_request.base.ref; console.log(`Base branch (PR target): ${baseBranch}`); console.log(`PR packages dir: ${prPackagesDir}`); console.log(`Base packages dir: ${basePackagesDir}`); console.log('Analyzing circular references in PR (merge) tree...'); const prCircularRefs = await analyzeCircularReferences(prPackagesDir); const prCount = prCircularRefs.length; console.log(`PR: Found ${prCount} circular references`); console.log(`Analyzing circular references in ${baseBranch} tree...`); const baseCircularRefs = await analyzeCircularReferences(basePackagesDir); const baseCount = baseCircularRefs.length; console.log(`Base: Found ${baseCount} circular references`); // Generate report const diff = prCount - baseCount; const markdownContent = generateMarkdownReport(prCircularRefs, baseCircularRefs, prCount, baseCount, baseBranch); // Post PR comment if (context.eventName === 'pull_request') { try { const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, }); const botComment = comments.find(comment => comment.user?.type === 'Bot' && comment.body?.includes('# ') && comment.body?.includes('Circular References Report') ); if (botComment) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: botComment.id, body: markdownContent }); console.log('Updated existing PR comment'); } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: markdownContent }); console.log('Created new PR comment'); } } catch (error) { console.error('Error posting PR comment:', error); } } // Log results but don't fail the build if (diff > 0) { console.log(`⚠️ Warning: This PR introduces ${diff} new circular ${diff === 1 ? 'reference' : 'references'}. Base: ${baseCount}, PR: ${prCount}`); } else if (diff < 0) { console.log(`✨ Great! This PR removes ${Math.abs(diff)} circular ${Math.abs(diff) === 1 ? 'reference' : 'references'}!`); } else { console.log(`✅ No change in circular references (${prCount})`); } } catch (error) { console.error('Error analyzing circular references:', error); core.setFailed('Failed to analyze circular references'); }