From 2b10efadb5c58e9ab51023920bc054d63edf45b6 Mon Sep 17 00:00:00 2001 From: Charles Hetterich Date: Sat, 1 Aug 2026 16:12:49 -0400 Subject: [PATCH 1/3] Remove dead API surface from cdm-builder and cdm-env Drop never-called exports (deployBatch, getOnChainCode, the flat toposort family, sync pvmContractBuild, computeCid, normalizeCdmJson, unused solidity helpers), the never-emitted check-cached event and cached status, reserved deploy options, the unused MetadataPublisher client param, hardcoded fake publish tx/block hashes, and the AssetHubConnection/BulletinConnection types. Constants are no longer re-exported from cdm-builder; import them from cdm-utils. Flat toposort tests are ported to the layered API. --- .changeset/remove-dead-surface.md | 7 ++ src/lib/contracts/src/builder.ts | 28 +---- src/lib/contracts/src/cdm-json.ts | 28 +---- src/lib/contracts/src/cid.ts | 10 -- src/lib/contracts/src/deployer.ts | 146 +--------------------- src/lib/contracts/src/detection.ts | 144 ++------------------- src/lib/contracts/src/index.ts | 27 +--- src/lib/contracts/src/pipeline.ts | 34 ++--- src/lib/contracts/src/publisher.ts | 16 +-- src/lib/contracts/src/solidity.ts | 16 +-- src/lib/contracts/tests/detection.test.ts | 13 +- src/lib/env/src/connection.ts | 10 -- src/lib/env/src/index.ts | 2 - src/lib/scripts/deploy-registry.ts | 4 +- 14 files changed, 52 insertions(+), 433 deletions(-) create mode 100644 .changeset/remove-dead-surface.md diff --git a/.changeset/remove-dead-surface.md b/.changeset/remove-dead-surface.md new file mode 100644 index 00000000..b2a197b6 --- /dev/null +++ b/.changeset/remove-dead-surface.md @@ -0,0 +1,7 @@ +--- +"@parity/cdm-builder": major +"@parity/cdm-env": patch +"@parity/cdm-cli": patch +--- + +Remove dead API surface. `@parity/cdm-builder` drops never-called exports (`ContractDeployer.deployBatch`, `getOnChainCode`, the flat toposort API `toposort`/`DeploymentOrder`/`createCrateToPackageMap`/`detectDeploymentOrder`, sync `pvmContractBuild`, `computeCid`, `normalizeCdmJson`, the solidity `readSolidityAbi`/`artifactDisplayPath`/`bytecodeSize` helpers), the never-emitted `check-cached` deploy event and `"cached"` summary status, the reserved-but-unused `waitFor`/`timeoutMs`/`gateway` deploy options, the constants re-exports (`GAS_LIMIT`/`STORAGE_DEPOSIT_LIMIT`/`CONTRACTS_REGISTRY_CRATE` — import from `@parity/cdm-utils` instead), the unused `MetadataPublisher` client constructor param, and the hardcoded fake `txHash`/`blockHash` on `publishBatch`/`publish-done`. `@parity/cdm-env` drops the unused `AssetHubConnection`/`BulletinConnection` types. `@parity/cdm-cli` sheds the unwired status-adapter observer API and unreachable UI states, stops running contract detection twice per build/deploy, and now shows where installed artifacts were saved after `cdm install`. diff --git a/src/lib/contracts/src/builder.ts b/src/lib/contracts/src/builder.ts index 8109ec58..2a3f13d6 100644 --- a/src/lib/contracts/src/builder.ts +++ b/src/lib/contracts/src/builder.ts @@ -1,5 +1,5 @@ import { resolve } from "path"; -import { execFileSync, spawn } from "child_process"; +import { spawn } from "child_process"; export interface BuildResult { crateName: string; @@ -16,36 +16,14 @@ export type BuildProgressCallback = ( ) => void; /** - * Build a single contract using `cargo pvm-contract build`. + * Build a single contract asynchronously with progress tracking, using + * `cargo pvm-contract build`. * * `registryAddress` is embedded into the contract via `CONTRACTS_REGISTRY_ADDR` * and must be resolved explicitly by the caller (CLI/pipeline) — there is no * implicit default, so an omitted address can never silently embed the wrong * network's registry. */ -export function pvmContractBuild( - rootDir: string, - crateName: string, - features: string | undefined, - registryAddress: string, -): void { - const manifestPath = resolve(rootDir, "Cargo.toml"); - const args = ["pvm-contract", "build", "--manifest-path", manifestPath, "-p", crateName]; - if (features) { - args.push("--features", features); - } - const env: Record = { - ...(process.env as Record), - CONTRACTS_REGISTRY_ADDR: registryAddress, - }; - execFileSync("cargo", args, { cwd: rootDir, stdio: "inherit", env }); -} - -/** - * Build a single contract asynchronously with progress tracking. - * - * See {@link pvmContractBuild} for why `registryAddress` is required. - */ export async function pvmContractBuildAsync( rootDir: string, crateName: string, diff --git a/src/lib/contracts/src/cdm-json.ts b/src/lib/contracts/src/cdm-json.ts index 98ad1144..dbbb3b46 100644 --- a/src/lib/contracts/src/cdm-json.ts +++ b/src/lib/contracts/src/cdm-json.ts @@ -14,17 +14,13 @@ export interface CdmJson { registry?: string; } -export function normalizeCdmJson(value: unknown): CdmJson { - return value as CdmJson; -} - export function readCdmJson(pathOrDir?: string): { cdmJson: CdmJson; cdmJsonPath: string } | null { const input = pathOrDir ?? process.cwd(); // If the input already points to a file, use it directly; otherwise treat as directory const candidate = input.endsWith(".json") ? resolve(input) : resolve(input, "cdm.json"); if (existsSync(candidate)) { const content = readFileSync(candidate, "utf-8"); - return { cdmJson: normalizeCdmJson(JSON.parse(content)), cdmJsonPath: candidate }; + return { cdmJson: JSON.parse(content) as CdmJson, cdmJsonPath: candidate }; } return null; } @@ -33,25 +29,3 @@ export function writeCdmJson(cdmJson: CdmJson, dir?: string): void { const target = resolve(dir ?? process.cwd(), "cdm.json"); writeFileSync(target, JSON.stringify(cdmJson, null, 2) + "\n"); } - -if (import.meta.vitest) { - const { describe, expect, test } = import.meta.vitest; - - describe("normalizeCdmJson", () => { - test("keeps manifests unchanged", () => { - const manifest = { - dependencies: { "@example/counter": "latest" }, - contracts: { - "@example/counter": { - version: 1, - address: "0x0000000000000000000000000000000000000001", - abi: [], - }, - }, - registry: "0x0000000000000000000000000000000000000002", - }; - - expect(normalizeCdmJson(manifest)).toEqual(manifest); - }); - }); -} diff --git a/src/lib/contracts/src/cid.ts b/src/lib/contracts/src/cid.ts index fec2784a..e332124b 100644 --- a/src/lib/contracts/src/cid.ts +++ b/src/lib/contracts/src/cid.ts @@ -1,17 +1,7 @@ -import { CID } from "multiformats/cid"; -import * as Digest from "multiformats/hashes/digest"; -import { blake2b } from "@noble/hashes/blake2.js"; import { BulletinPreparer, DEFAULT_CLIENT_CONFIG } from "@parity/product-sdk-cloud-storage"; -const BLAKE2B_256 = 0xb220; -const RAW_CODEC = 0x55; const bulletinPreparer = new BulletinPreparer(); -export function computeCid(data: Uint8Array): string { - const hash = blake2b(data, { dkLen: 32 }); - return CID.createV1(RAW_CODEC, Digest.create(BLAKE2B_256, hash)).toString(); -} - export async function computeBulletinStoreCid(data: Uint8Array): Promise { if (data.length > DEFAULT_CLIENT_CONFIG.chunkingThreshold) { const prepared = await bulletinPreparer.prepareStoreChunked(data); diff --git a/src/lib/contracts/src/deployer.ts b/src/lib/contracts/src/deployer.ts index d9617346..c0eff082 100644 --- a/src/lib/contracts/src/deployer.ts +++ b/src/lib/contracts/src/deployer.ts @@ -81,8 +81,8 @@ export const INSTANTIATE_WITH_CODE_STATIC_WEIGHT: WeightLike = { /** * Output of {@link ContractDeployer.planDeploy}. Exposes everything the * pipeline needs to emit a `deploy-plan` diagnostic event BEFORE submission, - * and everything `deployBatch` / `deployAndRegisterBatch` need to skip - * re-running the dry-run when the plan is passed back in. + * and everything `deployAndRegisterBatch` needs to skip re-running the + * dry-run when the plan is passed back in. * * `prepared[i].tx` is the fully-formed (unsigned) `Revive.instantiate_with_code` * call with the gas / storage limits already applied. @@ -308,26 +308,6 @@ export class ContractDeployer { }; } - /** - * Fetch the original uploaded bytecode for the contract at the given address. - * Uses ContractInfoOf → PristineCode to get the exact bytes that were deployed, - * avoiding any runtime transformation the pallet applies to stored code. - * Returns null if no contract exists or the query fails. - */ - async getOnChainCode(address: string): Promise { - try { - const addr = address as SizedHex<20>; - const info = await this.api.query.Revive.AccountInfoOf.getValue(addr); - if (!info || info.account_type.type !== "Contract") return null; - const codeHash = info.account_type.value.code_hash; - const pristine = await this.api.query.Revive.PristineCode.getValue(codeHash); - if (!pristine) return null; - return pristine; - } catch { - return null; - } - } - /** * Resolve the per-extrinsic weight budget used for weight-aware chunking. * Reads `System.BlockWeights().per_class.normal.max_extrinsic` from the @@ -356,13 +336,13 @@ export class ContractDeployer { * Pre-compute the dry-run + chunking decisions for a set of contracts * WITHOUT submitting anything on-chain. Returns the budget used, per-item * weights / addresses / storage deposits, the unsigned txs (so callers can - * pass the plan back into `deployBatch` / `deployAndRegisterBatch` to - * avoid re-doing the dry-run), and the chunk index groups. + * pass the plan back into `deployAndRegisterBatch` to avoid re-doing the + * dry-run), and the chunk index groups. * * Callers that need to inspect the plan (e.g. the pipeline's * `deploy-plan` diagnostic event) should call this, inspect the returned - * fields, then forward the plan to `deployBatch` / `deployAndRegisterBatch` - * via the optional `plan` arg so the dry-run work isn't duplicated. + * fields, then forward the plan to `deployAndRegisterBatch` via the + * optional `plan` arg so the dry-run work isn't duplicated. */ async planDeploy( pvmPaths: string[], @@ -383,120 +363,6 @@ export class ContractDeployer { return { budget, prepared, chunks }; } - /** - * Deploy multiple contracts, weight-aware-chunking them into one or more - * `Utility.batch_all` transactions. Each chunk stays atomic (the whole - * chunk reverts on any failure inside it); multiple chunks submit - * sequentially. - * - * Returns addresses in the same order as the input paths, plus the number - * of chunks submitted. When `onChunk` is provided, it's called - * synchronously after each chunk lands, with the chunk's crate names, - * addresses, and tx/block hashes. - * - * NOTE: Cross-chunk atomicity is lost — if chunk 1 lands and chunk 2 - * fails, chunk 1's deploys stay on-chain. Callers must treat each chunk's - * result as independent. - */ - async deployBatch( - pvmPaths: string[], - cdmPackages?: (string | undefined)[], - onChunk?: (result: { - crates: (string | undefined)[]; - addresses: string[]; - txHash: string; - blockHash: string; - chunkIndex: number; - totalChunks: number; - }) => void, - opts?: { - plan?: DeployPlan; - saltVersions?: (DeploySaltVersion | undefined)[]; - saltScope?: string; - }, - ): Promise<{ addresses: string[]; chunkCount: number }> { - if (pvmPaths.length === 0) return { addresses: [], chunkCount: 0 }; - - // 1. Dry-run all contracts up front so we have weights + CREATE2-style - // addresses for the chunker — unless a precomputed plan was passed in. - // 2. Chunk by cumulative declared weight (already done inside the plan). - const plan = - opts?.plan ?? - (await this.planDeploy(pvmPaths, cdmPackages, opts?.saltVersions, opts?.saltScope)); - const { prepared, chunks } = plan; - - const addresses: string[] = new Array(pvmPaths.length); - - // 3. Submit each chunk sequentially. - for (let ci = 0; ci < chunks.length; ci++) { - const idxs = chunks[ci]; - const label = `[AssetHub deploy chunk ${ci + 1}/${chunks.length}]`; - - // Fast path: a single-item chunk via the non-batch path — matches - // the pre-chunking one-contract behavior so a user deploying one - // contract doesn't pay the Utility.batch_all overhead. - let chunkResult: { txHash: string; blockHash: string; addrs: string[] }; - if (idxs.length === 1) { - const i = idxs[0]; - const r = await this.deploy( - pvmPaths[i], - cdmPackages?.[i], - opts?.saltVersions?.[i], - opts?.saltScope, - ); - addresses[i] = r.address; - chunkResult = { txHash: r.txHash, blockHash: r.blockHash, addrs: [r.address] }; - } else { - const result = await batchSubmitAndWatch( - idxs.map((i) => prepared[i].tx), - this.api, - this.signer, - { mode: "batch_all", waitFor: "best-block" }, - ); - if (!result.ok) { - throw new Error(`${label} Batch deploy failed: ${result.error.message}`, { - cause: result.error, - }); - } - const instantiated = this.api.event.Revive.Instantiated.filter( - result.value.events as Parameters< - typeof this.api.event.Revive.Instantiated.filter - >[0], - ); - if (instantiated.length !== idxs.length) { - throw new Error( - `${label} Expected ${idxs.length} Instantiated events, got ${instantiated.length}`, - ); - } - const chunkAddrs = instantiated.map( - (e: ReturnType[number]) => - e.payload.contract, - ); - for (let j = 0; j < idxs.length; j++) { - addresses[idxs[j]] = chunkAddrs[j]; - } - chunkResult = { - txHash: result.value.txHash, - blockHash: result.value.block.hash, - addrs: chunkAddrs, - }; - } - - if (onChunk) { - onChunk({ - crates: idxs.map((i) => cdmPackages?.[i]), - addresses: idxs.map((i) => addresses[i]), - txHash: chunkResult.txHash, - blockHash: chunkResult.blockHash, - chunkIndex: ci, - totalChunks: chunks.length, - }); - } - } - - return { addresses, chunkCount: chunks.length }; - } - /** * Deploy N contracts AND register each in the on-chain `ContractRegistry`, * weight-aware-chunking them into one or more `Utility.batch_all` diff --git a/src/lib/contracts/src/detection.ts b/src/lib/contracts/src/detection.ts index 5f174154..3e05f328 100644 --- a/src/lib/contracts/src/detection.ts +++ b/src/lib/contracts/src/detection.ts @@ -29,15 +29,6 @@ export interface ContractInfo { dependsOnCrates: string[]; } -export interface DeploymentOrder { - /** Crate names in deployment order */ - crateNames: string[]; - /** CDM package names in deployment order (null for contracts without CDM) */ - cdmPackages: (string | null)[]; - /** Full contract info for each contract, in deployment order */ - contracts: ContractInfo[]; -} - export interface DeploymentOrderLayered { /** Layers of crate names - each layer can be processed in parallel */ layers: string[][]; @@ -228,73 +219,6 @@ export function buildDependencyGraph(contracts: ContractInfo[]): Map): string[] { - const inDegree = new Map(); - const dependents = new Map(); - - for (const [node, deps] of graph) { - if (!inDegree.has(node)) { - inDegree.set(node, 0); - } - if (!dependents.has(node)) { - dependents.set(node, []); - } - - for (const dep of deps) { - if (!inDegree.has(dep)) { - inDegree.set(dep, 0); - } - if (!dependents.has(dep)) { - dependents.set(dep, []); - } - } - } - - for (const [node, deps] of graph) { - inDegree.set(node, deps.length); - for (const dep of deps) { - dependents.get(dep)!.push(node); - } - } - - const queue: string[] = []; - for (const [node, degree] of inDegree) { - if (degree === 0) { - queue.push(node); - } - } - queue.sort(); - - const result: string[] = []; - - while (queue.length > 0) { - queue.sort(); - const node = queue.shift()!; - result.push(node); - - for (const dependent of dependents.get(node) || []) { - const newDegree = inDegree.get(dependent)! - 1; - inDegree.set(dependent, newDegree); - if (newDegree === 0) { - queue.push(dependent); - } - } - } - - if (result.length !== inDegree.size) { - const remaining = [...inDegree.entries()] - .filter(([_, degree]) => degree > 0) - .map(([node]) => node); - throw new Error(`Circular dependency detected involving: ${remaining.join(", ")}`); - } - - return result; -} - /** * Topological sort (layered) using modified Kahn's algorithm. * Collects ALL zero-in-degree nodes at each iteration as a layer. @@ -354,41 +278,6 @@ export function toposortLayers(graph: Map): string[][] { return layers; } -/** - * Create a mapping from crate name to CDM package name. - */ -export function createCrateToPackageMap(contracts: ContractInfo[]): Map { - const map = new Map(); - for (const contract of contracts) { - if (contract.cdmPackage) { - map.set(contract.name, contract.cdmPackage); - } - } - return map; -} - -/** - * Detect contracts and determine deployment order based on dependencies. - * Uses cargo metadata for reliable workspace and dependency resolution. - */ -export function detectDeploymentOrder(rootDir: string): DeploymentOrder { - const contracts = detectContracts(rootDir); - const graph = buildDependencyGraph(contracts); - const sortedCrates = toposort(graph); - - const crateToPackage = createCrateToPackageMap(contracts); - const sortedPackages = sortedCrates.map((crate) => crateToPackage.get(crate) || null); - - const crateToContract = new Map(contracts.map((c) => [c.name, c])); - const sortedContracts = sortedCrates.map((crate) => crateToContract.get(crate)!); - - return { - crateNames: sortedCrates, - cdmPackages: sortedPackages, - contracts: sortedContracts, - }; -} - /** * Detect contracts and determine layered deployment order. * Each layer contains contracts that can be deployed in parallel. @@ -516,30 +405,6 @@ if (import.meta.vitest) { }); }); - describe("toposort", () => { - test("handles empty graph", () => { - const result = toposort(new Map()); - expect(result).toEqual([]); - }); - - test("handles linear chain", () => { - const graph = new Map([ - ["c", ["b"]], - ["b", ["a"]], - ["a", []], - ]); - expect(toposort(graph)).toEqual(["a", "b", "c"]); - }); - - test("detects circular dependencies", () => { - const graph = new Map([ - ["a", ["b"]], - ["b", ["a"]], - ]); - expect(() => toposort(graph)).toThrow("Circular dependency"); - }); - }); - describe("toposortLayers", () => { test("diamond graph", () => { const graph = new Map([ @@ -561,6 +426,15 @@ if (import.meta.vitest) { expect(result).toEqual([["A"], ["B"], ["C"]]); }); + test("linear chain declared in reverse still flattens dependencies-first", () => { + const graph = new Map([ + ["c", ["b"]], + ["b", ["a"]], + ["a", []], + ]); + expect(toposortLayers(graph).flat()).toEqual(["a", "b", "c"]); + }); + test("all independent", () => { const graph = new Map([ ["A", []], diff --git a/src/lib/contracts/src/index.ts b/src/lib/contracts/src/index.ts index a8f0e94c..ab1b78dd 100644 --- a/src/lib/contracts/src/index.ts +++ b/src/lib/contracts/src/index.ts @@ -1,14 +1,10 @@ export { type ContractInfo, type ContractToolchain, - type DeploymentOrder, type DeploymentOrderLayered, detectContracts, buildDependencyGraph, - toposort, toposortLayers, - createCrateToPackageMap, - detectDeploymentOrder, detectDeploymentOrderLayered, getGitRemoteUrl, readReadmeContent, @@ -52,12 +48,7 @@ export { writeBuildManifest, } from "./build-manifest"; -export { - type BuildResult, - type BuildProgressCallback, - pvmContractBuild, - pvmContractBuildAsync, -} from "./builder"; +export { type BuildResult, type BuildProgressCallback, pvmContractBuildAsync } from "./builder"; export { type AbiParam, @@ -74,16 +65,8 @@ export { export { MetadataPublisher } from "./publisher"; -export { computeCid } from "./cid"; - export { CONTRACTS_REGISTRY_ABI } from "./abi/registry"; -export { - GAS_LIMIT, - STORAGE_DEPOSIT_LIMIT, - CONTRACTS_REGISTRY_CRATE, -} from "@parity/cdm-utils"; - export { getCdmRoot, getContractDir, @@ -92,13 +75,7 @@ export { resolveContractAbiPath, } from "./store"; -export { - type CdmJsonContract, - type CdmJson, - normalizeCdmJson, - readCdmJson, - writeCdmJson, -} from "./cdm-json"; +export { type CdmJsonContract, type CdmJson, readCdmJson, writeCdmJson } from "./cdm-json"; export { type CdmLocalJson, readCdmLocalJson, resolveFeatures } from "./cdm-local-json"; diff --git a/src/lib/contracts/src/pipeline.ts b/src/lib/contracts/src/pipeline.ts index 814a04e5..7c15b6f1 100644 --- a/src/lib/contracts/src/pipeline.ts +++ b/src/lib/contracts/src/pipeline.ts @@ -135,11 +135,6 @@ export interface DeployContractsOptions { */ metadataSigner?: PolkadotSigner; - // Tuning (reserved for future use by internal tx layers) - waitFor?: "best-block" | "finalized"; - timeoutMs?: number; - gateway?: string; - onEvent?: (e: DeployEvent) => void; } @@ -150,7 +145,6 @@ export type DeployEvent = | { type: "build-progress"; crate: string; compiled: number; total?: number } | { type: "build-done"; crate: string; durationMs: number; bytecodeSize: number } | { type: "build-error"; crate: string; error: string } - | { type: "check-cached"; crate: string; address: HexString } | { type: "check-needs-deploy"; crate: string; address: HexString } | { /** @@ -224,7 +218,6 @@ export type DeployEvent = | { type: "publish-done"; cids: Record; - txHash: string; durationMs: number; } | { type: "pipeline-done"; summary: DeploySummary } @@ -236,7 +229,7 @@ export interface DeploySummary { cdmPackage?: string; address?: HexString; cid?: string; - status: "done" | "cached" | "error"; + status: "done" | "error"; error?: string; }>; totalDurationMs: number; @@ -893,7 +886,7 @@ export async function buildContracts(opts: BuildContractsOptions): Promise(); @@ -932,7 +925,6 @@ export async function deployContracts(opts: DeployContractsOptions): Promise status.get(c)?.status !== "cached"); - for (const crate of affected) { + for (const crate of layerDeployable) { const info = build.info.get(crate); failedCrates.add(crate); build.failed.add(crate); @@ -1245,7 +1231,7 @@ export async function deployContracts(opts: DeployContractsOptions): Promise ({ publishBatch: vi.fn(async (metadataList: unknown[]) => ({ cids: metadataList.map(() => "fakeCid123"), - txHash: "0xpublish", + blockNumber: 42, })), })), })); @@ -1685,11 +1671,7 @@ if (import.meta.vitest) { }); expect(events.indexOf("deploy-done:a")).toBeLessThan(events.indexOf("build-start:b")); - expect(mockMetadataPublisher).toHaveBeenCalledWith( - metadataSigner, - expect.anything(), - expect.anything(), - ); + expect(mockMetadataPublisher).toHaveBeenCalledWith(metadataSigner, expect.anything()); }); test("emits check-needs-deploy from the plan before deploy-plan and submission events", async () => { diff --git a/src/lib/contracts/src/publisher.ts b/src/lib/contracts/src/publisher.ts index 7acc182d..0c064f2f 100644 --- a/src/lib/contracts/src/publisher.ts +++ b/src/lib/contracts/src/publisher.ts @@ -25,7 +25,7 @@ export class MetadataPublisher { public signer: PolkadotSigner; public bulletinApi: CdmBulletinApi; - constructor(signer: PolkadotSigner, api: CdmBulletinApi, _client?: unknown) { + constructor(signer: PolkadotSigner, api: CdmBulletinApi) { this.signer = signer; this.bulletinApi = api; } @@ -54,11 +54,8 @@ export class MetadataPublisher { * Publish metadata for multiple contracts. Submits sequentially — one tx * per item — as required by Bulletin's nonce ordering. */ - async publishBatch( - metadataList: Metadata[], - ): Promise<{ cids: string[]; blockNumber: number; txHash: string; blockHash: string }> { - if (metadataList.length === 0) - return { cids: [], blockNumber: 0, txHash: "", blockHash: "" }; + async publishBatch(metadataList: Metadata[]): Promise<{ cids: string[]; blockNumber: number }> { + if (metadataList.length === 0) return { cids: [], blockNumber: 0 }; const N = metadataList.length; const cids: string[] = []; @@ -80,12 +77,7 @@ export class MetadataPublisher { lastBlockNumber = result.blockNumber; } - return { - cids, - blockNumber: lastBlockNumber, - txHash: "", - blockHash: "", - }; + return { cids, blockNumber: lastBlockNumber }; } } diff --git a/src/lib/contracts/src/solidity.ts b/src/lib/contracts/src/solidity.ts index 0d07b349..33c5cf7c 100644 --- a/src/lib/contracts/src/solidity.ts +++ b/src/lib/contracts/src/solidity.ts @@ -1,7 +1,6 @@ import { spawn, spawnSync } from "child_process"; -import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs"; import { basename, dirname, join, relative, resolve } from "path"; -import type { AbiEntry } from "./deployer"; import { findNamedMarkdown, findReadme } from "./detection"; import type { ContractInfo, ContractToolchain } from "./detection"; import { solidityLibraryFromImportPath } from "./solidity-imports"; @@ -884,19 +883,6 @@ export function hasBuildableSolidityProject(rootDir: string): boolean { return hasFoundryProject(rootDir) || hasHardhatProject(rootDir); } -export function readSolidityAbi(artifactPath: string): AbiEntry[] { - const artifact = readJson(artifactPath); - return Array.isArray(artifact?.abi) ? (artifact.abi as AbiEntry[]) : []; -} - -export function artifactDisplayPath(rootDir: string, path: string): string { - return relative(rootDir, path); -} - -export function bytecodeSize(path: string): number { - return statSync(path).size; -} - if (import.meta.vitest) { const { afterEach, describe, expect, test } = import.meta.vitest; const { mkdtempSync, rmSync } = await import("fs"); diff --git a/src/lib/contracts/tests/detection.test.ts b/src/lib/contracts/tests/detection.test.ts index 84ac23c6..84326365 100644 --- a/src/lib/contracts/tests/detection.test.ts +++ b/src/lib/contracts/tests/detection.test.ts @@ -1,5 +1,9 @@ import { afterEach, describe, test, expect } from "vitest"; -import { detectContracts, buildDependencyGraph, detectDeploymentOrder } from "../src/detection"; +import { + detectContracts, + buildDependencyGraph, + detectDeploymentOrderLayered, +} from "../src/detection"; import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import { resolve, dirname, join } from "node:path"; import { tmpdir } from "node:os"; @@ -25,9 +29,10 @@ describe("detection via cargo metadata", () => { }); test("topological sort puts counter first", () => { - const order = detectDeploymentOrder(TEMPLATE_DIR); - expect(order.crateNames[0]).toBe("counter"); - expect(order.crateNames.length).toBe(3); + const order = detectDeploymentOrderLayered(TEMPLATE_DIR); + const crateNames = order.layers.flat(); + expect(crateNames[0]).toBe("counter"); + expect(crateNames.length).toBe(3); }); test("CDM package names come from [package.metadata.cdm] in Cargo.toml", () => { diff --git a/src/lib/env/src/connection.ts b/src/lib/env/src/connection.ts index c79c5e16..559a032a 100644 --- a/src/lib/env/src/connection.ts +++ b/src/lib/env/src/connection.ts @@ -67,16 +67,6 @@ export type CdmAssetHubClient = { destroy: () => void; }; -export interface AssetHubConnection { - client: PolkadotClient; - api: CdmAssetHubApi; -} - -export interface BulletinConnection { - client: PolkadotClient; - api: CdmBulletinApi; -} - export interface CdmChainEndpoints { assethubUrl: string; bulletinUrl: string; diff --git a/src/lib/env/src/index.ts b/src/lib/env/src/index.ts index 64f9fae4..2cfc96f8 100644 --- a/src/lib/env/src/index.ts +++ b/src/lib/env/src/index.ts @@ -10,8 +10,6 @@ export { resolveQueryOrigin } from "./query_origin"; export { DEFAULT_NODE_URL } from "@parity/cdm-utils"; export type { - AssetHubConnection, - BulletinConnection, IpfsGateway, CdmChainClient, CdmAssetHubClient, diff --git a/src/lib/scripts/deploy-registry.ts b/src/lib/scripts/deploy-registry.ts index 6b834a59..e23cdab6 100644 --- a/src/lib/scripts/deploy-registry.ts +++ b/src/lib/scripts/deploy-registry.ts @@ -21,9 +21,9 @@ import { ss58Address, type CdmDeployAssetHubApi, } from "@parity/cdm-env"; -import { CONTRACTS_REGISTRY_PACKAGE } from "@parity/cdm-utils"; +import { CONTRACTS_REGISTRY_CRATE, CONTRACTS_REGISTRY_PACKAGE } from "@parity/cdm-utils"; import { getAccount } from "@parity/cdm-utils/accounts"; -import { ContractDeployer, CONTRACTS_REGISTRY_CRATE } from "@parity/cdm-builder"; +import { ContractDeployer } from "@parity/cdm-builder"; import { exportRegistrySnapshot, importRegistrySnapshot, From a50f9e1f5f116a3987a597740e3eaa19989885c9 Mon Sep 17 00:00:00 2001 From: Charles Hetterich Date: Sat, 1 Aug 2026 16:12:59 -0400 Subject: [PATCH 2/3] Remove dead CLI adapter surface and show install artifact paths Delete the unwired PipelineStatusAdapter observer API (onStatusChange, onPhaseChange, onLogChange, PhaseInfo, addressesFromSummary, unread fields), the unreachable checking/registering/cached UI states and their DeployTable branches, progressBar/formatDuration, the duplicate up-front detectBuildOrder call (the table now populates from the library's detect event), the unused InstallResult import/re-export, and the empty postInstallRust placeholder. cdm install now prints where each contract's artifacts were saved. --- src/apps/cli/src/commands/deploy.ts | 8 +- src/apps/cli/src/commands/install/index.ts | 10 +- src/apps/cli/src/commands/install/rust.ts | 1 - .../cli/src/lib/components/DeployTable.tsx | 80 +++----------- .../cli/src/lib/components/InstallTable.tsx | 12 +- src/apps/cli/src/lib/components/shared.tsx | 4 - src/apps/cli/src/lib/deploy-pipeline.ts | 103 ++---------------- src/apps/cli/src/lib/install-pipeline.ts | 3 +- src/apps/cli/src/lib/ui.ts | 58 ++-------- 9 files changed, 57 insertions(+), 222 deletions(-) delete mode 100644 src/apps/cli/src/commands/install/rust.ts diff --git a/src/apps/cli/src/commands/deploy.ts b/src/apps/cli/src/commands/deploy.ts index 0c4628b5..18916223 100644 --- a/src/apps/cli/src/commands/deploy.ts +++ b/src/apps/cli/src/commands/deploy.ts @@ -12,8 +12,12 @@ import { type CdmChainClient, } from "@parity/cdm-env"; import { getAccount } from "@parity/cdm-utils/accounts"; -import { ALICE_SS58, CONTRACTS_REGISTRY_PACKAGE } from "@parity/cdm-utils"; -import { ContractDeployer, CONTRACTS_REGISTRY_CRATE, resolveFeatures } from "@parity/cdm-builder"; +import { + ALICE_SS58, + CONTRACTS_REGISTRY_CRATE, + CONTRACTS_REGISTRY_PACKAGE, +} from "@parity/cdm-utils"; +import { ContractDeployer, resolveFeatures } from "@parity/cdm-builder"; import type { HexString } from "polkadot-api"; import { ensureAccountMapped } from "../lib/account-mapping"; import { runDeployWithUI, spinner } from "../lib/ui"; diff --git a/src/apps/cli/src/commands/install/index.ts b/src/apps/cli/src/commands/install/index.ts index ccbe02b4..08bf4ef3 100644 --- a/src/apps/cli/src/commands/install/index.ts +++ b/src/apps/cli/src/commands/install/index.ts @@ -19,13 +19,9 @@ import { } from "@parity/cdm-builder"; import { spinner } from "../../lib/ui"; import { runInstallWithUI } from "../../lib/install-pipeline"; -import type { InstallResult } from "../../lib/install-pipeline"; -import { postInstallRust } from "./rust"; import { postInstallSolidity } from "./solidity"; import { postInstallTypeScript } from "./typescript"; -export type { InstallResult } from "../../lib/install-pipeline"; - function detectProjectType(dir: string): { hasRust: boolean; hasSolidity: boolean; @@ -184,11 +180,9 @@ install.action(async (libraries: string[], rawOpts: InstallOptions) => { writeCdmJson(cdmJson); - // Run post-install hooks and update status line + // Run post-install hooks and update status line. Rust projects need no + // post-install step: cdm.json (written above) is all cdm::import! reads. if (results.length > 0) { - if (projectType.hasRust) { - await postInstallRust(); - } if (projectType.hasSolidity) { await postInstallSolidity(); } diff --git a/src/apps/cli/src/commands/install/rust.ts b/src/apps/cli/src/commands/install/rust.ts deleted file mode 100644 index be69c79f..00000000 --- a/src/apps/cli/src/commands/install/rust.ts +++ /dev/null @@ -1 +0,0 @@ -export async function postInstallRust(): Promise {} diff --git a/src/apps/cli/src/lib/components/DeployTable.tsx b/src/apps/cli/src/lib/components/DeployTable.tsx index 16e47ac2..d089da48 100644 --- a/src/apps/cli/src/lib/components/DeployTable.tsx +++ b/src/apps/cli/src/lib/components/DeployTable.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from "react"; import { Box, Text } from "ink"; -import type { ContractStatus, PhaseInfo } from "../deploy-pipeline"; +import type { ContractStatus } from "../deploy-pipeline"; import { Link, LinkLine, @@ -12,7 +12,6 @@ import { Idle, Done, Failed, - Cached, LogTail, truncateAddress, shortHash, @@ -26,11 +25,9 @@ const COL_PHASE = 5; const COL_ADDR = 14; /** Infer which phase failed based on what fields exist on the status */ -function errorPhase(s: ContractStatus): "build" | "deploy" | "metadata" | "register" { - // Register failed: both deploy and publish completed, error during register - if (s.address && s.publishTxHash) return "register"; - // Deploy completed but publish didn't: metadata/publish failure - if (s.address && !s.publishTxHash && s.cid) return "metadata"; +function errorPhase(s: ContractStatus): "build" | "deploy" | "metadata" { + // Deploy completed: metadata/publish failure + if (s.address && s.cid) return "metadata"; if (s.bytecodeSize !== undefined) return "deploy"; // Build completed: deploy phase (or parallel deploy+publish) failed if ( @@ -81,9 +78,9 @@ function ContractRow({ } else if (state === "waiting") { buildCell = ; } else { - // built/deploying/deployed/publishing/registering/done — show completed bar - // plus the compiled bytecode size (base-10 kB/MB) if the library - // populated `bytecodeSize` on the status. + // built/deploying/done — show completed bar plus the compiled + // bytecode size (base-10 kB/MB) if the library populated + // `bytecodeSize` on the status. const bp = s?.buildProgress; if (bp?.total && bp.total > 0) { buildCell = ( @@ -109,52 +106,19 @@ function ContractRow({ ); } - // Cached state — show cache indicator across all deploy columns - if (state === "cached") { - return ( - - - - {name} - - - {buildCell} - - - - - - - - - - - {s?.address ? {truncateAddress(s.address)} : } - - - ); - } - // Deploy column — use deployInProgress flag for spinner let deployCell: React.ReactNode; - if (state === "checking") { - deployCell = ; - } else if (s?.deployInProgress) { + if (s?.deployInProgress) { deployCell = ; } else if (state === "error" && errorPhase(s!) === "deploy") { deployCell = ; - } else if ( - ["registering", "done"].includes(state) && - s?.deployTxHash && - s?.deployBlockHash && - assethubUrl - ) { + } else if (state === "done" && s?.deployTxHash && s?.deployBlockHash && assethubUrl) { deployCell = ( {shortHash(s.deployTxHash)} ); - } else if (["registering", "done"].includes(state)) { + } else if (state === "done") { deployCell = ; } else { deployCell = ; @@ -166,14 +130,12 @@ function ContractRow({ metaCell = ; } else if (state === "error" && errorPhase(s!) === "metadata") { metaCell = ; - } else if (["registering", "done"].includes(state) && s?.cid && ipfsGatewayUrl) { + } else if (state === "done" && s?.cid && ipfsGatewayUrl) { metaCell = ( {shortHash(s.cid)} ); - } else if (["registering", "done"].includes(state) && s?.publishTxHash) { - metaCell = ; } else { metaCell = ; } @@ -182,8 +144,6 @@ function ContractRow({ let registerCell: React.ReactNode; if (s?.registerInProgress) { registerCell = ; - } else if (state === "error" && errorPhase(s!) === "register") { - registerCell = ; } else if (state === "done" && s?.registerTxHash && s?.registerBlockHash && assethubUrl) { registerCell = ( @@ -226,15 +186,10 @@ function ContractRow({ // full link on its own line below the row. Conditions mirror the cell // rendering above so an inline line appears iff a cell shows a link. const linkDefs: { label: string; url: string }[] = []; - if ( - ["registering", "done"].includes(state) && - s?.deployTxHash && - s?.deployBlockHash && - assethubUrl - ) { + if (state === "done" && s?.deployTxHash && s?.deployBlockHash && assethubUrl) { linkDefs.push({ label: "deploy", url: pjsExplorerUrl(assethubUrl, s.deployBlockHash) }); } - if (["registering", "done"].includes(state) && s?.cid && ipfsGatewayUrl) { + if (state === "done" && s?.cid && ipfsGatewayUrl) { linkDefs.push({ label: "metadata", url: ipfsUrl(ipfsGatewayUrl, s.cid) }); } if (state === "done" && s?.registerTxHash && s?.registerBlockHash && assethubUrl) { @@ -261,7 +216,6 @@ function ContractRow({ export interface DeployTableProps { statuses: Map; displayNames: Map; - crates: string[]; buildOnly: boolean; assethubUrl?: string; bulletinUrl?: string; @@ -273,7 +227,6 @@ export interface DeployTableProps { export function DeployTable({ statuses, displayNames, - crates, buildOnly, assethubUrl, ipfsGatewayUrl, @@ -287,10 +240,9 @@ export function DeployTable({ return () => clearInterval(timer); }, []); - const rowCrates = [ - ...crates, - ...Array.from(statuses.keys()).filter((crate) => !crates.includes(crate)), - ]; + // Row order comes from `statuses` insertion order, which the adapter + // fills in layered deployment order on the `detect` event. + const rowCrates = Array.from(statuses.keys()); // Collect and group errors for display below table. Toolchain-level // failures often apply to every contract in a build batch, and printing diff --git a/src/apps/cli/src/lib/components/InstallTable.tsx b/src/apps/cli/src/lib/components/InstallTable.tsx index 51269229..8ea4d599 100644 --- a/src/apps/cli/src/lib/components/InstallTable.tsx +++ b/src/apps/cli/src/lib/components/InstallTable.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import { relative } from "node:path"; import { Box, Text } from "ink"; import type { InstallStatus } from "../install-pipeline"; import { @@ -87,12 +88,17 @@ function InstallRow({ // When OSC 8 hyperlinks aren't available, the metadata cell only shows the // short CID hash; surface the full link on its own line below the row. - const hasLink = state === "done" && s?.metadataCid && ipfsGatewayUrl; - if (hyperlinksSupported || !hasLink) return row; + const showLinkLine = + !hyperlinksSupported && state === "done" && s?.metadataCid && ipfsGatewayUrl; + // After a successful install, show where the artifacts landed. + const savedPath = + state === "done" && s?.savedPath ? relative(process.cwd(), s.savedPath) : null; + if (!showLinkLine && !savedPath) return row; return ( {row} - + {showLinkLine && } + {savedPath && {` ↳ ${savedPath}`}} ); } diff --git a/src/apps/cli/src/lib/components/shared.tsx b/src/apps/cli/src/lib/components/shared.tsx index 9ec00284..f8b788dd 100644 --- a/src/apps/cli/src/lib/components/shared.tsx +++ b/src/apps/cli/src/lib/components/shared.tsx @@ -92,10 +92,6 @@ export function Failed() { return ; } -export function Cached() { - return ~; -} - export function LogTail({ lines, height, diff --git a/src/apps/cli/src/lib/deploy-pipeline.ts b/src/apps/cli/src/lib/deploy-pipeline.ts index 7518731f..ef4f1aad 100644 --- a/src/apps/cli/src/lib/deploy-pipeline.ts +++ b/src/apps/cli/src/lib/deploy-pipeline.ts @@ -1,10 +1,4 @@ -import type { - BuildEvent, - DeployEvent, - BuildSummary, - DeploySummary, - ContractInfo, -} from "@parity/cdm-builder"; +import type { BuildEvent, DeployEvent } from "@parity/cdm-builder"; /** * CLI-local `ContractStatus` shape that the Ink `DeployTable.tsx` component @@ -14,16 +8,7 @@ import type { * The pipeline itself now lives in `@parity/cdm-builder`; this file only handles * event → UI-status translation so the terminal table stays unchanged. */ -export type ContractState = - | "waiting" - | "building" - | "built" - | "checking" - | "cached" - | "deploying" - | "registering" - | "done" - | "error"; +export type ContractState = "waiting" | "building" | "built" | "deploying" | "done" | "error"; export interface ContractStatus { crateName: string; @@ -33,8 +18,6 @@ export interface ContractStatus { cid?: string; deployTxHash?: string; deployBlockHash?: string; - publishTxHash?: string; - publishBlockHash?: string; /** Same value as `deployTxHash` since deploy+register are one batch now. */ registerTxHash?: string; registerBlockHash?: string; @@ -47,53 +30,23 @@ export interface ContractStatus { registerInProgress?: boolean; } -/** - * Current "phase" signal emitted by the library to describe dead time between - * build and per-row deploy spinners. Mirrors the `DeployEvent.phase` variant - * shape; the adapter stores the latest phase on itself and invokes - * `onPhaseChange` so the Ink UI can render a spinner above the table. - */ -export interface PhaseInfo { - name: - | "connecting-registry" - | "checking-versions" - | "precomputing-addresses" - | "preparing-metadata" - | "deploying" - | "publishing" - | "done"; - description: string; - layer?: number; -} - export interface AdapterOptions { - /** Called on every status mutation (after the update is applied). */ - onStatusChange?: (crateName: string, status: ContractStatus) => void; /** Called when a build reveals a crate's CDM package name. */ onCdmPackageDetected?: (crateName: string, cdmPackage: string) => void; - /** Called when the library emits a `phase` event. */ - onPhaseChange?: (phase: PhaseInfo | null) => void; - /** Called when a process log line is appended to the retained tail. */ - onLogChange?: (lines: string[]) => void; } /** - * Build/deploy adapter — maintains a `Map` that the Ink - * UI reads, plus an `onEvent` handler to pass into `buildContracts()` or - * `deployContracts()`. Also exposes `crates` / `layers` / `contracts` so the UI - * can render the table layout as soon as detection completes. + * Build/deploy adapter — maintains a `Map` and a log + * tail that the Ink UI reads on each render tick, plus an `onEvent` handler + * to pass into `buildContracts()` or `deployContracts()`. Rows appear as the + * library's `detect` event populates `statuses`. */ export class PipelineStatusAdapter { static readonly LOG_TAIL_LINES = 5; readonly statuses = new Map(); readonly logLines: string[] = []; - crates: string[] = []; - layers: string[][] = []; - contracts: ContractInfo[] = []; cdmPackageMap = new Map(); - /** Most recent `phase` event (null until first phase event fires). */ - phase: PhaseInfo | null = null; constructor(private opts: AdapterOptions = {}) {} @@ -104,20 +57,15 @@ export class PipelineStatusAdapter { if (this.logLines.length > PipelineStatusAdapter.LOG_TAIL_LINES) { this.logLines.splice(0, this.logLines.length - PipelineStatusAdapter.LOG_TAIL_LINES); } - this.opts.onLogChange?.([...this.logLines]); } private clearLogs() { - if (this.logLines.length === 0) return; this.logLines.splice(0); - this.opts.onLogChange?.([]); } private update(crate: string, state: ContractState, extra?: Partial) { const current = this.statuses.get(crate) ?? { crateName: crate, state: "waiting" }; - const updated: ContractStatus = { ...current, state, ...extra }; - this.statuses.set(crate, updated); - this.opts.onStatusChange?.(crate, updated); + this.statuses.set(crate, { ...current, state, ...extra }); } /** Forward a `BuildEvent` (emitted by `buildContracts()`) into the UI map. */ @@ -127,15 +75,11 @@ export class PipelineStatusAdapter { this.appendLog(e.line); return; case "detect": - this.contracts = e.contracts; - this.layers = e.layers; - this.crates = e.layers.flat(); for (const c of e.contracts) { if (c.cdmPackage) this.cdmPackageMap.set(c.name, c.cdmPackage); } - for (const crate of this.crates) { + for (const crate of e.layers.flat()) { this.statuses.set(crate, { crateName: crate, state: "waiting" }); - this.opts.onStatusChange?.(crate, this.statuses.get(crate)!); } for (const [crate, pkg] of this.cdmPackageMap) { this.opts.onCdmPackageDetected?.(crate, pkg); @@ -193,26 +137,16 @@ export class PipelineStatusAdapter { case "build-error": this.handleBuildEvent(e as BuildEvent); return; - case "check-cached": - this.update(e.crate, "cached", { address: e.address }); - return; case "check-needs-deploy": // Address precomputed — no state change yet, deploy-register // will follow. return; case "deploy-plan": - // Diagnostic-only — no per-crate state change. The CLI's - // `runDeployWithUI` logs the event to stderr so the user can - // see the real budget vs per-contract weights on their next - // run. Nothing to mutate here. + // Diagnostic-only — no per-crate state change and nothing to + // mutate here. return; case "phase": - this.phase = { - name: e.name, - description: e.description, - layer: e.layer, - }; - this.opts.onPhaseChange?.(this.phase); + // Coarse progress signal — not surfaced in the table UI. return; case "sign-request": // Not forwarded to UI for now — `deploy-register-start` / @@ -267,7 +201,6 @@ export class PipelineStatusAdapter { this.update(crate, existing?.state ?? "done", { publishInProgress: false, cid, - publishTxHash: e.txHash, }); } return; @@ -290,21 +223,9 @@ export class PipelineStatusAdapter { return; } }; - - /** Snapshot of addresses suitable for the old `PipelineResult` consumers. */ - addressesFromSummary(summary: DeploySummary | BuildSummary): Record { - const out: Record = {}; - for (const c of (summary as DeploySummary).contracts) { - const dc = c as { crate: string; address?: string }; - if (dc.address) out[dc.crate] = dc.address; - } - return out; - } } -const ANSI_PATTERN = - // biome-ignore lint/suspicious/noControlCharactersInRegex: terminal log sanitization. - /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g; +const ANSI_PATTERN = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1B\\))/g; function cleanLogLine(line: string): string { return line.replace(ANSI_PATTERN, "").replace(/\r/g, "").trimEnd(); diff --git a/src/apps/cli/src/lib/install-pipeline.ts b/src/apps/cli/src/lib/install-pipeline.ts index c60fc00b..8ef1ff1d 100644 --- a/src/apps/cli/src/lib/install-pipeline.ts +++ b/src/apps/cli/src/lib/install-pipeline.ts @@ -4,12 +4,11 @@ import { installContracts, type InstallContractsOptions, type InstallEvent, - type InstallResult, type InstallSummary, } from "@parity/cdm-builder"; import { InstallTable } from "./components/InstallTable"; -export type { InstallResult, InstallSummary } from "@parity/cdm-builder"; +export type { InstallSummary } from "@parity/cdm-builder"; export type InstallState = "waiting" | "querying" | "fetching" | "done" | "error"; diff --git a/src/apps/cli/src/lib/ui.ts b/src/apps/cli/src/lib/ui.ts index b7c92a42..b88a70e9 100644 --- a/src/apps/cli/src/lib/ui.ts +++ b/src/apps/cli/src/lib/ui.ts @@ -3,7 +3,6 @@ import { render } from "ink"; import { buildContracts, deployContracts, - detectBuildOrder, type BuildContractsOptions, type DeployContractsOptions, type BuildSummary, @@ -42,16 +41,6 @@ export function spinner(label: string, detail: string) { }; } -export function progressBar(current: number, total: number, width: number = 20): string { - if (total === 0) return "░".repeat(width); - const filled = Math.round((current / total) * width); - return "█".repeat(filled) + "░".repeat(width - filled); -} - -export function formatDuration(ms: number): string { - return `${(ms / 1000).toFixed(1)}s`; -} - export interface BuildUIOptions extends Omit {} export interface DeployUIOptions extends Omit { @@ -61,10 +50,8 @@ export interface DeployUIOptions extends Omit } interface RenderArgs { - statuses: Map; + adapter: PipelineStatusAdapter; displayNames: Map; - crates: string[]; - logLines: string[]; buildOnly: boolean; assethubUrl?: string; bulletinUrl?: string; @@ -74,10 +61,9 @@ interface RenderArgs { function makeUI(args: RenderArgs) { return render( React.createElement(DeployTable, { - statuses: args.statuses, + statuses: args.adapter.statuses, displayNames: args.displayNames, - crates: args.crates, - logLines: args.logLines, + logLines: args.adapter.logLines, buildOnly: args.buildOnly, assethubUrl: args.assethubUrl, bulletinUrl: args.bulletinUrl, @@ -86,43 +72,24 @@ function makeUI(args: RenderArgs) { ); } -function precomputeBuildDisplay(rootDir: string, contracts: string[] | undefined) { - const order = detectBuildOrder(rootDir, contracts); - const crates = order.layers.flat(); - const displayNames = new Map(); - for (const contract of order.contracts) { - displayNames.set( - contract.name, - contract.cdmPackage ?? contract.displayName ?? contract.name, - ); - } - return { crates, displayNames }; -} - /** * Run `buildContracts()` and render progress into the Ink `DeployTable`. * - * The table layout is populated lazily from the `detect` event — crates, layers, - * and CDM package names are all supplied by the library, not derived up-front - * in the CLI. + * The table layout is populated from the library's `detect` event — the + * adapter fills `statuses` (row order) and `displayNames` in place, and the + * table re-reads both on every render tick. Nothing is detected up-front in + * the CLI. */ export async function runBuildWithUI(opts: BuildUIOptions): Promise<{ summary: BuildSummary; result: PipelineResult; }> { - const { crates, displayNames } = precomputeBuildDisplay(opts.rootDir, opts.contracts); - + const displayNames = new Map(); const adapter = new PipelineStatusAdapter({ onCdmPackageDetected: (crate, pkg) => displayNames.set(crate, pkg), }); - const app = makeUI({ - statuses: adapter.statuses, - displayNames, - crates, - logLines: adapter.logLines, - buildOnly: true, - }); + const app = makeUI({ adapter, displayNames, buildOnly: true }); let summary: BuildSummary; try { @@ -153,17 +120,14 @@ export async function runDeployWithUI(opts: DeployUIOptions): Promise<{ summary: DeploySummary; result: PipelineResult; }> { - const { crates, displayNames } = precomputeBuildDisplay(opts.rootDir, opts.contracts); - + const displayNames = new Map(); const adapter = new PipelineStatusAdapter({ onCdmPackageDetected: (crate, pkg) => displayNames.set(crate, pkg), }); const app = makeUI({ - statuses: adapter.statuses, + adapter, displayNames, - crates, - logLines: adapter.logLines, buildOnly: false, assethubUrl: opts.assethubUrl, bulletinUrl: opts.bulletinUrl, From 59442e63d93bc889f03f1b9ce1a4f0b7ab63fa31 Mon Sep 17 00:00:00 2001 From: Charles Hetterich Date: Sat, 1 Aug 2026 16:13:03 -0400 Subject: [PATCH 3/3] Remove dead frontend code and placeholder assets Drop the fabricated weeklyCalls stat, the unused Package.versions field, placeholder Docs/Playground header links, no-op biome-ignore comments (the biome linter is disabled), the empty App.css/index.css husks, and the keyword-chip hover styling orphaned by the plain-span change. Replace the stock Vite README with a real one and share a single splitPackageName helper between PackagePage and PackageCard. --- src/apps/frontend/README.md | 76 ++----------------- src/apps/frontend/src/App.css | 1 - src/apps/frontend/src/components/Header.tsx | 6 +- .../frontend/src/components/PackageCard.tsx | 19 +---- .../frontend/src/components/SkeletonCard.tsx | 1 - .../frontend/src/data/registry-queries.ts | 2 - src/apps/frontend/src/data/types.ts | 2 - src/apps/frontend/src/index.css | 1 - src/apps/frontend/src/lib/package-name.ts | 9 +++ src/apps/frontend/src/pages/PackagePage.css | 10 --- src/apps/frontend/src/pages/PackagePage.tsx | 16 +--- src/apps/frontend/src/pages/SearchPage.tsx | 1 - 12 files changed, 18 insertions(+), 126 deletions(-) delete mode 100644 src/apps/frontend/src/App.css delete mode 100644 src/apps/frontend/src/index.css create mode 100644 src/apps/frontend/src/lib/package-name.ts diff --git a/src/apps/frontend/README.md b/src/apps/frontend/README.md index d2e77611..b299c1ea 100644 --- a/src/apps/frontend/README.md +++ b/src/apps/frontend/README.md @@ -1,73 +1,7 @@ -# React + TypeScript + Vite +# @parity/cdm-frontend -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +Web dashboard for the CDM on-chain contract registry: browse and search published +contracts, view readmes, ABIs, versions, and dependencies (React 19 + Vite). -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh - -## React Compiler - -The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). - -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: - -```js -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - - // Remove tseslint.configs.recommended and replace with this - tseslint.configs.recommendedTypeChecked, - // Alternatively, use this for stricter rules - tseslint.configs.strictTypeChecked, - // Optionally, add this for stylistic rules - tseslint.configs.stylisticTypeChecked, - - // Other configs... - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` - -You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: - -```js -// eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{ts,tsx}'], - extends: [ - // Other configs... - // Enable lint rules for React - reactX.configs['recommended-typescript'], - // Enable lint rules for React DOM - reactDom.configs.recommended, - ], - languageOptions: { - parserOptions: { - project: ['./tsconfig.node.json', './tsconfig.app.json'], - tsconfigRootDir: import.meta.dirname, - }, - // other options... - }, - }, -]) -``` +- `pnpm dev` (from the repo root) — start the dev server with workspace deps built +- `pnpm --filter @parity/cdm-frontend build` — production build diff --git a/src/apps/frontend/src/App.css b/src/apps/frontend/src/App.css deleted file mode 100644 index 67783503..00000000 --- a/src/apps/frontend/src/App.css +++ /dev/null @@ -1 +0,0 @@ -/* This file is intentionally left empty. App styles are in component/page CSS files */ diff --git a/src/apps/frontend/src/components/Header.tsx b/src/apps/frontend/src/components/Header.tsx index 7f9ecad1..148b281b 100644 --- a/src/apps/frontend/src/components/Header.tsx +++ b/src/apps/frontend/src/components/Header.tsx @@ -5,11 +5,7 @@ import "./Header.css"; const REPO_URL = "https://github.com/paritytech/contract-dependency-manager"; -const EXTERNAL_LINKS: { label: string; href: string }[] = [ - { label: "Docs", href: REPO_URL }, - { label: "Github", href: REPO_URL }, - { label: "Playground", href: "https://playground.dot" }, -]; +const EXTERNAL_LINKS: { label: string; href: string }[] = [{ label: "Github", href: REPO_URL }]; export default function Header() { return ( diff --git a/src/apps/frontend/src/components/PackageCard.tsx b/src/apps/frontend/src/components/PackageCard.tsx index 2bfc44a7..177054a4 100644 --- a/src/apps/frontend/src/components/PackageCard.tsx +++ b/src/apps/frontend/src/components/PackageCard.tsx @@ -1,5 +1,6 @@ import { Link } from "react-router-dom"; import { metadataCidFromUri } from "../data/registry-queries"; +import { splitPackageName } from "../lib/package-name"; import type { Package } from "../data/types"; import "./PackageCard.css"; @@ -8,19 +9,6 @@ interface PackageCardProps { linkTarget?: string; } -function formatCalls(n: number): string { - return n.toLocaleString(); -} - -function splitPackageName(name: string): { prefix: string; leaf: string } { - const idx = name.lastIndexOf("/"); - if (idx < 0) return { prefix: "", leaf: name }; - return { - prefix: name.slice(0, idx + 1), - leaf: name.slice(idx + 1), - }; -} - export default function PackageCard({ pkg, linkTarget }: PackageCardProps) { const metadataLoading = !!metadataCidFromUri(pkg.metadataUri) && !pkg.metadataLoaded; const name = splitPackageName(pkg.name); @@ -54,11 +42,6 @@ export default function PackageCard({ pkg, linkTarget }: PackageCardProps) { ) : ( )} - {pkg.weeklyCalls != null && ( - - {formatCalls(pkg.weeklyCalls)} weekly calls - - )} {pkg.publishedDate ? ( {pkg.publishedDate} ) : metadataLoading ? ( diff --git a/src/apps/frontend/src/components/SkeletonCard.tsx b/src/apps/frontend/src/components/SkeletonCard.tsx index 87f9d317..75da806b 100644 --- a/src/apps/frontend/src/components/SkeletonCard.tsx +++ b/src/apps/frontend/src/components/SkeletonCard.tsx @@ -27,7 +27,6 @@ export function SkeletonGrid({ count = 9 }: SkeletonGridProps) { return ( diff --git a/src/apps/frontend/src/data/registry-queries.ts b/src/apps/frontend/src/data/registry-queries.ts index e0c97dd1..3fe8d8a5 100644 --- a/src/apps/frontend/src/data/registry-queries.ts +++ b/src/apps/frontend/src/data/registry-queries.ts @@ -53,7 +53,6 @@ export async function queryContractByName( return { name, version: String(latestVersion), - weeklyCalls: 0, address: unwrapOption(addressResult.value), metadataUri: unwrapOption(metadataResult.value), metadataLoaded: false, @@ -87,7 +86,6 @@ function parseContractEntry(value: unknown): Package | null { return { name, version: String(Number(version ?? 0)), - weeklyCalls: 0, address: typeof address === "string" ? address : undefined, metadataUri: typeof metadataUri === "string" ? metadataUri : undefined, metadataLoaded: false, diff --git a/src/apps/frontend/src/data/types.ts b/src/apps/frontend/src/data/types.ts index 684590bc..3ed945be 100644 --- a/src/apps/frontend/src/data/types.ts +++ b/src/apps/frontend/src/data/types.ts @@ -18,7 +18,6 @@ export interface Package { version: string; description?: string; author?: string; - weeklyCalls?: number; license?: string; keywords?: string[]; publishedDate?: string; @@ -27,7 +26,6 @@ export interface Package { homepage?: string; readme?: string; dependencies?: Record; - versions?: { version: string; date: string }[]; abi?: AbiEntry[]; address?: string; metadataUri?: string; diff --git a/src/apps/frontend/src/index.css b/src/apps/frontend/src/index.css deleted file mode 100644 index 98ce4e51..00000000 --- a/src/apps/frontend/src/index.css +++ /dev/null @@ -1 +0,0 @@ -/* This file is intentionally left empty. Global styles are in styles/global.css */ diff --git a/src/apps/frontend/src/lib/package-name.ts b/src/apps/frontend/src/lib/package-name.ts new file mode 100644 index 00000000..af42e6e5 --- /dev/null +++ b/src/apps/frontend/src/lib/package-name.ts @@ -0,0 +1,9 @@ +/** Split a CDM package name into its scope prefix ("@org/") and leaf part. */ +export function splitPackageName(name: string): { prefix: string; leaf: string } { + const idx = name.lastIndexOf("/"); + if (idx < 0) return { prefix: "", leaf: name }; + return { + prefix: name.slice(0, idx + 1), + leaf: name.slice(idx + 1), + }; +} diff --git a/src/apps/frontend/src/pages/PackagePage.css b/src/apps/frontend/src/pages/PackagePage.css index 91d1e7d3..ee28110a 100644 --- a/src/apps/frontend/src/pages/PackagePage.css +++ b/src/apps/frontend/src/pages/PackagePage.css @@ -334,16 +334,6 @@ border-radius: 999px; color: var(--color-text-secondary); font-size: 12px; - text-decoration: none; - transition: - background-color 0.1s ease, - color 0.1s ease; -} - -.sidebar-keyword:hover { - background: rgba(255, 255, 255, 0.08); - color: var(--color-text-primary); - text-decoration: none; } /* ============ Status pages ============ */ diff --git a/src/apps/frontend/src/pages/PackagePage.tsx b/src/apps/frontend/src/pages/PackagePage.tsx index 87e9ed6c..8722114e 100644 --- a/src/apps/frontend/src/pages/PackagePage.tsx +++ b/src/apps/frontend/src/pages/PackagePage.tsx @@ -5,6 +5,7 @@ import DOMPurify from "dompurify"; import Layout from "../components/Layout"; import { CopyIcon, CheckIcon } from "../components/Icons"; import { handleExternalClick } from "../lib/external-link"; +import { splitPackageName } from "../lib/package-name"; import { usePackage } from "../hooks/usePackage"; import { usePackageVersions } from "../hooks/usePackageVersions"; import type { PackageVersionInfo } from "../data/registry-queries"; @@ -55,12 +56,6 @@ function getBadgeClass(entry: AbiEntry): string { } } -function splitName(name: string): { prefix: string; leaf: string } { - const idx = name.lastIndexOf("/"); - if (idx < 0) return { prefix: "", leaf: name }; - return { prefix: name.slice(0, idx + 1), leaf: name.slice(idx + 1) }; -} - function shortAddress(addr: string): string { if (addr.length <= 14) return addr; return `${addr.slice(0, 8)}…${addr.slice(-6)}`; @@ -76,7 +71,6 @@ function ParamType({ param, depth = 0 }: { param: AbiParam; depth?: number }) { {param.components.map((c, i) => ( {inputs.map((p, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: param index is stable {p.name || `_${i}`} @@ -167,7 +160,6 @@ function AbiEntryCard({ entry }: { entry: AbiEntry }) { {outputs.map((p, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: param index is stable {p.name || `_${i}`} @@ -309,7 +301,6 @@ function VersionsTab({ pkg, versions, loading, error }: VersionsTabProps) { return (
{Array.from({ length: 3 }).map((_, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: fixed decorative array ))}
@@ -374,7 +365,6 @@ function PackageBody({ pkg, activeTab, setActiveTab }: PackageBodyProps) { (pkg.readme ? (
{Array.from({ length: 5 }).map((_, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: fixed decorative array ))}
@@ -449,7 +438,6 @@ function PackageSkeleton() {