From 078baee1baaa3999695461e886be9102afaf91c6 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Thu, 29 Oct 2026 08:08:40 -0400 Subject: [PATCH] refactor(upgrades): remove unreachable executed-task link dropdown Co-authored-by: Toshi J-Engine-Session: toshi:a5789614-b6af-453d-8496-0287e8b486b8 --- README.md | 37 +-------------- src/components/UpgradeSelection.tsx | 59 +---------------------- src/lib/__tests__/deployments.test.ts | 49 +++++--------------- src/lib/deployments.ts | 67 ++------------------------- src/lib/types/shared.ts | 6 --- 5 files changed, 20 insertions(+), 198 deletions(-) diff --git a/README.md b/README.md index 15636fe..cb13e2b 100644 --- a/README.md +++ b/README.md @@ -75,20 +75,13 @@ Key requirements and notes: - "OP" → `op.json` - **Task origin signatures**: By default, tasks require three signature files stored per-network in `tasks//signatures//`: `creator-signature.json`, `base-facilitator-signature.json`, and `base-sc-facilitator-signature.json`. Signatures are stored separately from the config directory (which is what the tarball is signed over) so signing does not change the signed content. Use the `genTaskOriginSig.ts` script to generate these. Set `skipTaskOriginValidation: true` in the validation config to opt out. - **Script execution**: The tool executes Foundry from `active/evm`. Ensure your Foundry project or script context is available under the per-network config directory (`tasks//config//`); the tool will run the `cmd` specified in your validation config. Temporary outputs like `temp-script-output.txt` will be written there. -- **Optional README parsing**: If `tasks//config//README.md` exists, the tool may parse it to display status and execution links. +- **Optional README parsing**: If `tasks//config//README.md` exists, the tool may parse it to display status. ### Task README structure When present, each task's `README.md` is parsed to populate the UI. Place it at `active/evm/tasks//config//README.md` and follow these rules: -- **Status line (required for status display)**: Include a line containing `Status:` within the first 20 lines. Recognized values are `PENDING`, `READY TO SIGN`, and `EXECUTED` (case-insensitive). If `EXECUTED` is not present and `READY TO SIGN` is not present, the status is treated as `PENDING`. Supported formats: - - **Single-line with link (any one of these)**: - - `Status: EXECUTED (https://explorer/tx/0x...)` - - `Status: [EXECUTED](https://explorer/tx/0x...)` - - `Status: EXECUTED https://explorer/tx/0x...` - - **Multi-line links (up to 5 lines after the status line)**: - - First line: `Status: EXECUTED` - - Next lines: `Label: https://...` (e.g., `Transaction: https://...`, `Proposal: https://...`) +- **Status line (required for status display)**: Include a line containing `Status:` within the first 20 lines. Recognized values are `PENDING`, `READY TO SIGN`, and `EXECUTED` (case-insensitive). If `EXECUTED` is not present and `READY TO SIGN` is not present, the status is treated as `PENDING`. - **Description (optional but recommended)**: Add a `## Description` section. The first paragraph after this header is shown in the UI and should be concise (aim for ≤150 chars). Formatting like bold, italics, and inline code is stripped. @@ -122,32 +115,6 @@ Status: READY TO SIGN Multisig proposal prepared; awaiting signatures from designated signers. ``` -Executed with single-line link: - -```markdown -# Upgrade System Config - -Status: EXECUTED (https://explorer/tx/0xabc123) - -## Description - -Executed upgrade to System Config with no parameter changes to gas config. -``` - -Executed with multiple labeled links: - -```markdown -# Upgrade System Config - -Status: EXECUTED -Transaction: https://explorer/tx/0xabc123 -Proposal: https://snapshot.org/#/proposal/0xdef456 - -## Description - -Executed upgrade; links include both onchain transaction and governance proposal. -``` - ### Validation file structure Validation configs live under each task directory at `active/evm/tasks//config//validations/` and are selected by user type: diff --git a/src/components/UpgradeSelection.tsx b/src/components/UpgradeSelection.tsx index b30ca6b..2ac5ee1 100644 --- a/src/components/UpgradeSelection.tsx +++ b/src/components/UpgradeSelection.tsx @@ -1,16 +1,10 @@ -import { TaskStatus, Upgrade, ExecutionLink } from '@/lib/types'; +import { Upgrade } from '@/lib/types'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { MarkdownText } from './MarkdownText'; import { Card } from './ui/Card'; import { Badge } from './ui/Badge'; import { SectionHeader } from './ui/SectionHeader'; -const STATUS_VARIANT_MAP: Record = { - [TaskStatus.Executed]: 'success', - [TaskStatus.ReadyToSign]: 'warning', - [TaskStatus.Pending]: 'neutral', -}; - interface UpgradeSelectionProps { onSelect: (upgrade: Upgrade) => void; } @@ -179,7 +173,7 @@ export const UpgradeSelection: React.FC = ({ onSelect })
- + {option.status}
@@ -189,52 +183,3 @@ export const UpgradeSelection: React.FC = ({ onSelect }) ); }; - -const StatusBadge: React.FC<{ - status?: TaskStatus; - executionLinks?: ExecutionLink[]; -}> = ({ status, executionLinks }) => { - if (!status) return null; - - const variant = STATUS_VARIANT_MAP[status] || 'neutral'; - - if (status === TaskStatus.Executed && executionLinks && executionLinks.length > 0) { - return ( -
- - {status} {executionLinks.length > 1 && `(${executionLinks.length})`} - - - - - -
- {executionLinks.map((link, i) => ( - e.stopPropagation()} - > - {link.label} - - ))} -
-
- ); - } - - return {status}; -}; diff --git a/src/lib/__tests__/deployments.test.ts b/src/lib/__tests__/deployments.test.ts index a7fc4e6..c21b0eb 100644 --- a/src/lib/__tests__/deployments.test.ts +++ b/src/lib/__tests__/deployments.test.ts @@ -2,11 +2,7 @@ import fs from 'fs/promises'; import os from 'os'; import path from 'path'; import { afterEach, beforeEach, describe, expect, it } from '@jest/globals'; -import { - getUpgradeOptions, - normalizeUrl, - resetContractDeploymentsRootCacheForTests, -} from '../deployments'; +import { getUpgradeOptions, resetContractDeploymentsRootCacheForTests } from '../deployments'; import { NetworkType, TaskStatus } from '../types'; let originalCwd: string; @@ -24,37 +20,6 @@ afterEach(async () => { await fs.rm(tempDir, { recursive: true, force: true }); }); -describe('normalizeUrl', () => { - it.each([ - ['https://etherscan.io/tx/0x123', 'https://etherscan.io/tx/0x123'], - ['http://example.com/path', 'http://example.com/path'], - [' https://example.com ', 'https://example.com'], - ['https://example.com/path).', 'https://example.com/path'], - ['https://example.com/path],', 'https://example.com/path'], - ['https://example.com/path...', 'https://example.com/path'], - ['https://example.com/path?foo=bar&baz=qux', 'https://example.com/path?foo=bar&baz=qux'], - ['https://example.com/path#section', 'https://example.com/path#section'], - ['https://example.com:8080/path', 'https://example.com:8080/path'], - ['https://user:pass@example.com/path', 'https://user:pass@example.com/path'], - ])('normalizes %p to %p', (input, expected) => { - expect(normalizeUrl(input)).toBe(expected); - }); - - it.each([ - '', - 'http', - 'http://', - 'httpxyz://not-valid', - 'http:not-a-url', - 'not a url at all', - 'ftp://files.example.com', - 'javascript:alert(1)', - 'file:///etc/passwd', - ])('rejects %p', input => { - expect(normalizeUrl(input)).toBeUndefined(); - }); -}); - describe('getUpgradeOptions', () => { async function makeTask(id: string, network: string, readme?: string) { const taskDir = path.join(tempDir, 'active', 'evm', 'tasks', id); @@ -104,13 +69,21 @@ describe('getUpgradeOptions', () => { expect(upgrades).toEqual([expect.objectContaining({ status: TaskStatus.Pending })]); }); - it('omits execution links from serialized executed tasks with no URLs', async () => { + it('returns executed status for a task with an executed README status', async () => { await makeTask('2025-06-04-upgrade-foo', 'mainnet', 'Status: EXECUTED'); const upgrades = getUpgradeOptions(NetworkType.Mainnet); expect(upgrades).toEqual([expect.objectContaining({ status: TaskStatus.Executed })]); - expect(JSON.parse(JSON.stringify(upgrades[0]))).not.toHaveProperty('executionLinks'); + }); + + it('omits status when the README has no status line', async () => { + await makeTask('2025-06-04-upgrade-foo', 'mainnet', '# Upgrade Foo'); + + const upgrades = getUpgradeOptions(NetworkType.Mainnet); + + expect(upgrades).toHaveLength(1); + expect(JSON.parse(JSON.stringify(upgrades[0]))).not.toHaveProperty('status'); }); it('returns all active task folders that have validations for the selected network', async () => { diff --git a/src/lib/deployments.ts b/src/lib/deployments.ts index e5ab490..f2f6466 100644 --- a/src/lib/deployments.ts +++ b/src/lib/deployments.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import { NetworkType, TaskStatus, Upgrade, ExecutionLink } from './types'; +import { NetworkType, TaskStatus, Upgrade } from './types'; let cachedRoot: string | null = null; @@ -42,7 +42,6 @@ export function findContractDeploymentsRoot(): string { const DEFAULT_DESCRIPTION = 'Smart contract upgrade deployment'; const MAX_STATUS_SEARCH_LINES = 20; -const MAX_STATUS_FOLLOW_UP_LINES = 5; function formatUpgradeName(folderName: string): string { const slug = folderName.replace(/^\d{4}-\d{2}-\d{2}-/, ''); @@ -97,24 +96,7 @@ function extractDescription(content: string): string { } } -export function normalizeUrl(rawUrl: string): string | undefined { - if (!rawUrl) return undefined; - const trimmed = rawUrl.trim().replace(/[)\].,]+$/, ''); - if (!trimmed.startsWith('http://') && !trimmed.startsWith('https://')) { - return undefined; - } - try { - new URL(trimmed); - return trimmed; - } catch { - return undefined; - } -} - -function parseExecutionStatus(content: string): { - status?: TaskStatus; - executionLinks?: ExecutionLink[]; -} { +function parseExecutionStatus(content: string): { status?: TaskStatus } { try { const lines = content.replace(/\r\n/g, '\n').split('\n'); const statusLineIndex = lines @@ -129,46 +111,8 @@ function parseExecutionStatus(content: string): { const isExecuted = normalizedStatus.includes(TaskStatus.Executed); const isReady = normalizedStatus.includes(TaskStatus.ReadyToSign); - if (!isExecuted) return { status: isReady ? TaskStatus.ReadyToSign : TaskStatus.Pending }; - - const executionLinks: ExecutionLink[] = []; - const seenUrls = new Set(); - - const addLink = (label: string, maybeUrl: string | undefined) => { - const url = maybeUrl ? normalizeUrl(maybeUrl) : undefined; - if (!url || seenUrls.has(url)) return; - seenUrls.add(url); - executionLinks.push({ label: label || 'Transaction', url }); - }; - - for (const match of statusLine.matchAll(/https?:\/\/[^\s)\]]+/g)) { - addLink('Transaction', match[0]); - } - - for ( - let i = statusLineIndex + 1; - i < lines.length && i <= statusLineIndex + MAX_STATUS_FOLLOW_UP_LINES; - i++ - ) { - const rawLine = lines[i].trim(); - if (!rawLine) break; - if (rawLine.startsWith('#')) break; - - const labelledMatch = rawLine.match(/^([^:]+):\s*(https?:\/\/\S+)/i); - if (labelledMatch) { - addLink(labelledMatch[1].trim(), labelledMatch[2]); - continue; - } - - const urlMatch = rawLine.match(/https?:\/\/\S+/); - if (urlMatch) { - addLink('Transaction', urlMatch[0]); - } - } - - return executionLinks.length > 0 - ? { status: TaskStatus.Executed, executionLinks } - : { status: TaskStatus.Executed }; + if (isExecuted) return { status: TaskStatus.Executed }; + return { status: isReady ? TaskStatus.ReadyToSign : TaskStatus.Pending }; } catch (error) { console.error('parseExecutionStatus error:', error); return {}; @@ -214,13 +158,12 @@ function getUpgradeOption( try { const content = fs.readFileSync(readmePath, 'utf-8'); - const { status, executionLinks } = parseExecutionStatus(content); + const { status } = parseExecutionStatus(content); return { ...baseInfo, name: extractTitle(content) || baseInfo.name, description: extractDescription(content), status, - executionLinks, }; } catch (parseError) { console.error(`Error parsing ${readmePath}:`, parseError); diff --git a/src/lib/types/shared.ts b/src/lib/types/shared.ts index f49fffb..7aa6e79 100644 --- a/src/lib/types/shared.ts +++ b/src/lib/types/shared.ts @@ -11,11 +11,6 @@ export enum TaskStatus { Pending = 'PENDING', } -export interface ExecutionLink { - url: string; - label: string; -} - export interface Upgrade { id: string; name: string; @@ -23,5 +18,4 @@ export interface Upgrade { date: string; network: NetworkType; status?: TaskStatus; - executionLinks?: ExecutionLink[]; }