diff --git a/.github/workflows/sync-trustwallet-assets.yml b/.github/workflows/sync-trustwallet-assets.yml new file mode 100644 index 000000000..8f5c70864 --- /dev/null +++ b/.github/workflows/sync-trustwallet-assets.yml @@ -0,0 +1,50 @@ +name: Sync + +on: + workflow_dispatch: + schedule: + - cron: '0 0 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: write + +jobs: + main: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + + - uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1 + with: + node-version-file: '.nvmrc' + + - run: corepack enable pnpm + + - run: pnpm install --frozen-lockfile + + - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 + with: + path: workspace/sync-trustwallet-assets/repo + repository: trustwallet/assets + ref: master + + - run: pnpm turbo run sync + working-directory: workspace/sync-trustwallet-assets + + - uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38 # v5.0.2 + with: + commit-message: 'sync(assets): trustwallet/assets' + title: 'sync(assets): trustwallet/assets' + committer: Frontmatter Bot <${{ github.actor }}@users.noreply.github.com> + author: Frontmatter Bot <${{ github.actor }}@users.noreply.github.com> + body: | + #### What this PR does / why we need it: + + Sync latest changes from `trustwallet/assets` repository. + + branch: sync/trustwallet/assets diff --git a/.idea/frontmatter.iml b/.idea/frontmatter.iml index ce189958b..96142281e 100644 --- a/.idea/frontmatter.iml +++ b/.idea/frontmatter.iml @@ -5,6 +5,8 @@ + + diff --git a/packages/example/package.json b/packages/example/package.json index 1bc73218a..ce36ff187 100644 --- a/packages/example/package.json +++ b/packages/example/package.json @@ -5,7 +5,6 @@ "repository": { "url": "git+https://github.com/levaintech/frontmatter" }, - "type": "module", "main": "./dist/index.js", "source": "./src/index.ts", "types": "./dist/index.d.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9bde65982..be0b49277 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,16 @@ importers: specifier: ^0.5.6 version: 0.5.6(prettier@3.0.3) + workspace/sync-trustwallet-assets: + dependencies: + yaml: + specifier: ^2.3.4 + version: 2.3.4 + devDependencies: + '@workspace/tsconfig': + specifier: workspace:* + version: link:../tsconfig + workspace/tsconfig: {} packages: @@ -4258,6 +4268,11 @@ packages: engines: {node: '>= 14'} dev: true + /yaml@2.3.4: + resolution: {integrity: sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==} + engines: {node: '>= 14'} + dev: false + /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} diff --git a/workspace/eslint-config/index.js b/workspace/eslint-config/index.js index 926944808..1eba5a72a 100644 --- a/workspace/eslint-config/index.js +++ b/workspace/eslint-config/index.js @@ -1,10 +1,14 @@ module.exports = [ + { + ignores: ['**/dist/*'], + }, require('@eslint/js').configs.recommended, { files: ['**/*.ts', '**/*.tsx'], plugins: { '@typescript-eslint': require('@typescript-eslint/eslint-plugin'), }, + rules: require('@typescript-eslint/eslint-plugin').configs.recommended.rules, languageOptions: { parser: require('@typescript-eslint/parser'), }, diff --git a/workspace/sync-trustwallet-assets/.gitignore b/workspace/sync-trustwallet-assets/.gitignore new file mode 100644 index 000000000..55d62c108 --- /dev/null +++ b/workspace/sync-trustwallet-assets/.gitignore @@ -0,0 +1 @@ +repo/ \ No newline at end of file diff --git a/workspace/sync-trustwallet-assets/package.json b/workspace/sync-trustwallet-assets/package.json new file mode 100644 index 000000000..ea5890fbc --- /dev/null +++ b/workspace/sync-trustwallet-assets/package.json @@ -0,0 +1,26 @@ +{ + "name": "@workspace/sync-trustwallet-assets", + "version": "0.0.0", + "private": true, + "scripts": { + "build": "tsc --project tsconfig.build.json", + "clean": "rm -rf dist", + "lint": "eslint .", + "sync": "node dist/index.js" + }, + "lint-staged": { + "*": [ + "prettier --write --ignore-unknown" + ], + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ] + }, + "dependencies": { + "yaml": "^2.3.4" + }, + "devDependencies": { + "@workspace/tsconfig": "workspace:*" + } +} diff --git a/workspace/sync-trustwallet-assets/src/gitlog.ts b/workspace/sync-trustwallet-assets/src/gitlog.ts new file mode 100644 index 000000000..6b4673e8a --- /dev/null +++ b/workspace/sync-trustwallet-assets/src/gitlog.ts @@ -0,0 +1,17 @@ +import { exec } from 'node:child_process'; + +export function getAuthorName(filepath: string): Promise { + return new Promise((resolve, reject) => { + exec(`git log -1 --pretty=format:"%ae" ${filepath}`, (error, stdout, stderr) => { + if (error) { + reject(error); + return; + } + if (stderr) { + reject(stderr); + return; + } + resolve(stdout); + }); + }); +} diff --git a/workspace/sync-trustwallet-assets/src/index.ts b/workspace/sync-trustwallet-assets/src/index.ts new file mode 100644 index 000000000..f3bc2cc6b --- /dev/null +++ b/workspace/sync-trustwallet-assets/src/index.ts @@ -0,0 +1,12 @@ +import { join } from 'node:path'; +import process from 'node:process'; + +import { Eip155Sync } from './sync'; + +export async function sync(): Promise { + const cwd = process.cwd(); + + await new Eip155Sync(join(cwd, 'repo/blockchains/ethereum/assets'), join(cwd, '../../packages/eip155/1')).sync(); +} + +void sync(); diff --git a/workspace/sync-trustwallet-assets/src/sync.ts b/workspace/sync-trustwallet-assets/src/sync.ts new file mode 100644 index 000000000..6fcc59362 --- /dev/null +++ b/workspace/sync-trustwallet-assets/src/sync.ts @@ -0,0 +1,110 @@ +import { copyFile, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises'; + +import { stringify } from 'yaml'; + +import { getAuthorName } from './gitlog'; + +interface Info { + name: string; + website: string; + description: string; + explorer: string; + type: string; + symbol: string; + decimals: number; + status: string; + id: string; + tags: string[]; + links: { + name: string; + url: string; + }[]; +} + +export abstract class Sync { + constructor( + protected readonly from: string, + protected readonly to: string, + ) {} + + async sync() { + const dirs = await readdir(this.from); + // TODO(fuxingloh): Limit to first 100 files for testing + for (const dir of dirs.slice(0, 100)) { + const info = JSON.parse( + await readFile(`${this.from}/${dir}/info.json`, { + encoding: 'utf-8', + }), + ) as Partial; + + if (!(await this.shouldWrite(dir, info))) continue; + await this.write(dir, info); + } + } + + async shouldWrite(dir: string, info: Partial): Promise { + // Make sure logo.png and info.json exists + const fromLogoStats = await stat(`${this.from}/${dir}/logo.png`); + const fromInfoStats = await stat(`${this.from}/${dir}/info.json`); + if (!fromLogoStats.isFile() || !fromInfoStats.isFile()) return false; + + // If README.md and logo.png do not exist on the other side, write it + const namespace = this.getNamespace(info); + const toLogoStats = await stat(`${this.to}/${namespace}/logo.png`); + const toReadmeStats = await stat(`${this.to}/${namespace}/README.md`); + if (!toLogoStats.isFile() || !toReadmeStats.isFile()) return true; + + // Otherwise, allow overwriting if the author is Frontmatter Bot + const name = await getAuthorName(`${this.to}/${namespace}/README.md`); + return name === 'Frontmatter Bot'; + } + + abstract write(dir: string, info: Partial): Promise; + + abstract getNamespace(info: Partial): string; + + createLinks(info: Partial): Info['links'] { + const links: Info['links'] = []; + if (info.website) links.push({ name: 'website', url: info.website }); + if (info.explorer) links.push({ name: 'explorer', url: info.explorer }); + + if (info.links) { + for (const link of info.links) { + if (link.name === 'website' || link.name === 'explorer') continue; + if (!link.url?.startsWith('https://')) continue; + if (!link.name) continue; + links.push(link); + } + } + return links; + } +} + +export class Eip155Sync extends Sync { + async write(dir: string, info: Partial): Promise { + const namespace = this.getNamespace(info); + await mkdir(`${this.to}/${namespace}`, { recursive: true }); + await copyFile(`${this.from}/${dir}/logo.png`, `${this.to}/${namespace}/logo.png`); + await writeFile( + `${this.to}/${namespace}/README.md`, + [ + `---`, + stringify({ + symbol: info.symbol, + decimals: info.decimals, + tags: info.tags, + links: this.createLinks(info), + }), + `---`, + '', + `# ${info.name}`, + '', + info.description, + ].join('\n'), + ); + } + + getNamespace(info: Partial): string { + return info.type!.toLowerCase() + '/' + info.id; + } +} diff --git a/workspace/sync-trustwallet-assets/tsconfig.build.json b/workspace/sync-trustwallet-assets/tsconfig.build.json new file mode 100644 index 000000000..4c94b410f --- /dev/null +++ b/workspace/sync-trustwallet-assets/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src"], + "exclude": ["**/*.unit.ts"] +} diff --git a/workspace/sync-trustwallet-assets/tsconfig.json b/workspace/sync-trustwallet-assets/tsconfig.json new file mode 100644 index 000000000..1799e7475 --- /dev/null +++ b/workspace/sync-trustwallet-assets/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@workspace/tsconfig" +} diff --git a/workspace/sync-trustwallet-assets/turbo.json b/workspace/sync-trustwallet-assets/turbo.json new file mode 100644 index 000000000..55b4485ce --- /dev/null +++ b/workspace/sync-trustwallet-assets/turbo.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://turborepo.org/schema.json", + "extends": ["//"], + "pipeline": { + "sync": { + "dependsOn": ["build"] + } + } +} diff --git a/workspace/tsconfig/tsconfig.json b/workspace/tsconfig/tsconfig.json index 5922d84c4..f7949d2f9 100644 --- a/workspace/tsconfig/tsconfig.json +++ b/workspace/tsconfig/tsconfig.json @@ -4,7 +4,7 @@ "compilerOptions": { "lib": ["ES2021"], "target": "ES2020", - "module": "ES2020", + "module": "CommonJS", "allowJs": true, "composite": false, "declaration": true,