diff --git a/.cspell.json b/.cspell.json index 87ac470..b25cb90 100644 --- a/.cspell.json +++ b/.cspell.json @@ -1,45 +1,11 @@ { "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", "version": "0.2", - "ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log", "/lib"], + "ignorePaths": ["**/*.json", "**/*.css", "node_modules", "**/*.log"], "useGitignore": true, "language": "en", - "words": [ - "binkey", - "binsec", - "chainlist", - "cirip", - "dataurl", - "devpool", - "ethersproject", - "fract", - "gnosisscan", - "godb", - "greyscale", - "IERC", - "keccak", - "keypair", - "libsodium", - "Numberish", - "outdir", - "Rpcs", - "scalarmult", - "servedir", - "solmate", - "sonarjs", - "typebox", - "TYPEHASH", - "ubiquibot", - "UBIQUIBOT", - "URLSAFE", - "WXDAI", - "XDAI", - "xmark", - "wfzpewmlyiozupulbuur", - "SUPABASE", - "Knip" - ], - "dictionaries": ["typescript", "node", "software-terms", "html"], + "words": ["dataurl", "devpool", "outdir", "servedir", "TYPEHASH", "noopener", "noreferrer", "reown", "appkit", "zksync", "worldchain", "Caip", "UUSD"], + "dictionaries": ["typescript", "node", "software-terms"], "import": ["@cspell/dict-typescript/cspell-ext.json", "@cspell/dict-node/cspell-ext.json", "@cspell/dict-software-terms"], "ignoreRegExpList": ["[0-9a-fA-F]{6}"] } diff --git a/.env.example b/.env.example index 20bc751..e69de29 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +0,0 @@ -# your Supabase url, e.g. https://wfzpewmlyiozupulbuur.supabase.co -SUPABASE_URL="" -# your Supabase Anon Key that you can find under settings/api, e.g eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndmenBld21seWlvenVwdWxidXVyIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTU2NzQzMzksImV4cCI6MjAxMTI1MDMzOX0.SKIL3Q0NOBaMehH0ekFspwgcu3afp3Dl9EDzPqs1nKs -SUPABASE_ANON_KEY="" diff --git a/.eslintrc b/.eslintrc index 5996f04..7d20576 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,19 +4,22 @@ "parserOptions": { "project": ["./tsconfig.json"] }, - "plugins": ["@typescript-eslint", "sonarjs"], + "plugins": ["@typescript-eslint", "sonarjs", "filename-rules"], "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:sonarjs/recommended"], "ignorePatterns": ["**/*.js"], "rules": { + "filename-rules/match": [2, "/^(e2e\\.ts$|.*\\/e2e\\.ts$|[a-z0-9]+(?:[-._a-z0-9]+)*\\.ts|\\.[a-z0-9]+)$/"], "prefer-arrow-callback": ["warn", { "allowNamedFunctions": true }], "func-style": ["warn", "declaration", { "allowArrowFunctions": false }], "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-non-null-assertion": "error", "constructor-super": "error", "no-invalid-this": "off", - "@typescript-eslint/no-invalid-this": ["error"], + "@typescript-eslint/no-invalid-this": "error", "no-restricted-syntax": ["error", "ForInStatement"], "use-isnan": "error", + "no-unneeded-ternary": "error", + "no-nested-ternary": "error", "@typescript-eslint/no-unused-vars": [ "error", { @@ -38,16 +41,15 @@ "sonarjs/no-identical-expressions": "error", "@typescript-eslint/naming-convention": [ "error", - { "selector": "interface", "format": ["PascalCase"], "custom": { "regex": "^I[A-Z]", "match": false } }, - { "selector": "memberLike", "modifiers": ["private"], "format": ["camelCase"], "leadingUnderscore": "require" }, - { "selector": "typeLike", "format": ["PascalCase"] }, - { "selector": "typeParameter", "format": ["PascalCase"], "prefix": ["T"] }, - { "selector": "variable", "format": ["camelCase", "UPPER_CASE"], "leadingUnderscore": "allow", "trailingUnderscore": "allow" }, - { "selector": "variable", "format": ["camelCase"], "leadingUnderscore": "allow", "trailingUnderscore": "allow" }, + { "selector": "interface", "format": ["StrictPascalCase"], "custom": { "regex": "^I[A-Z]", "match": false } }, + { "selector": "memberLike", "modifiers": ["private"], "format": ["strictCamelCase"], "leadingUnderscore": "require" }, + { "selector": "typeLike", "format": ["StrictPascalCase"] }, + { "selector": "typeParameter", "format": ["StrictPascalCase"], "prefix": ["T"] }, + { "selector": "variable", "format": ["strictCamelCase", "UPPER_CASE"], "leadingUnderscore": "allow", "trailingUnderscore": "allow" }, { "selector": "variable", "modifiers": ["destructured"], "format": null }, - { "selector": "variable", "types": ["boolean"], "format": ["PascalCase"], "prefix": ["is", "should", "has", "can", "did", "will", "does"] }, - { "selector": "variableLike", "format": ["camelCase"] }, - { "selector": ["function", "variable"], "format": ["camelCase"] } + { "selector": "variable", "types": ["boolean"], "format": ["StrictPascalCase"], "prefix": ["is", "should", "has", "can", "did", "will", "does"] }, + { "selector": "variableLike", "format": ["strictCamelCase"] }, + { "selector": ["function", "variable"], "format": ["strictCamelCase"] } ] } } diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ffa5b88..8b13789 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @rndquu + diff --git a/.github/empty-string-checker.ts b/.github/empty-string-checker.ts new file mode 100644 index 0000000..ff94e81 --- /dev/null +++ b/.github/empty-string-checker.ts @@ -0,0 +1,129 @@ +import * as core from "@actions/core"; +import { Octokit } from "@octokit/rest"; +import simpleGit from "simple-git"; + +const token = process.env.GITHUB_TOKEN; +const [owner, repo] = process.env.GITHUB_REPOSITORY?.split("/") || []; +const pullNumber = process.env.GITHUB_PR_NUMBER || process.env.PULL_REQUEST_NUMBER || "0"; +const baseRef = process.env.GITHUB_BASE_REF; + +if (!token || !owner || !repo || pullNumber === "0" || !baseRef) { + core.setFailed("Missing required environment variables."); + process.exit(1); +} + +const octokit = new Octokit({ auth: token }); +const git = simpleGit(); + +async function main() { + try { + const { data: pullRequest } = await octokit.pulls.get({ + owner, + repo, + pull_number: parseInt(pullNumber, 10), + }); + + const baseSha = pullRequest.base.sha; + const headSha = pullRequest.head.sha; + + await git.fetch(["origin", baseSha, headSha]); + + const diff = await git.diff([`${baseSha}...${headSha}`]); + + core.info("Checking for empty strings..."); + const violations = parseDiffForEmptyStrings(diff); + + if (violations.length > 0) { + violations.forEach(({ file, line, content }) => { + core.warning( + "Detected an empty string.\n\nIf this is during variable initialization, consider using a different approach.\nFor more information, visit: https://www.github.com/ubiquity/ts-template/issues/31", + { + file, + startLine: line, + } + ); + }); + + // core.setFailed(`${violations.length} empty string${violations.length > 1 ? "s" : ""} detected in the code.`); + + await octokit.rest.checks.create({ + owner, + repo, + name: "Empty String Check", + head_sha: headSha, + status: "completed", + conclusion: violations.length > 0 ? "failure" : "success", + output: { + title: "Empty String Check Results", + summary: `Found ${violations.length} violation${violations.length !== 1 ? "s" : ""}`, + annotations: violations.map((v) => ({ + path: v.file, + start_line: v.line, + end_line: v.line, + annotation_level: "warning", + message: "Empty string found", + raw_details: v.content, + })), + }, + }); + } else { + core.info("No empty strings found."); + } + } catch (error) { + core.setFailed(`An error occurred: ${error instanceof Error ? error.message : String(error)}`); + } +} + +function parseDiffForEmptyStrings(diff: string) { + const violations: Array<{ file: string; line: number; content: string }> = []; + const diffLines = diff.split("\n"); + + let currentFile: string; + let headLine = 0; + let inHunk = false; + + diffLines.forEach((line) => { + const hunkHeaderMatch = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line); + if (hunkHeaderMatch) { + headLine = parseInt(hunkHeaderMatch[1], 10); + inHunk = true; + return; + } + + if (line.startsWith("--- a/") || line.startsWith("+++ b/")) { + currentFile = line.slice(6); + inHunk = false; + return; + } + + // Only process TypeScript files + if (!currentFile?.endsWith(".ts")) { + return; + } + + if (inHunk && line.startsWith("+")) { + // Check for empty strings in TypeScript syntax + if (/^\+.*""/.test(line)) { + // Ignore empty strings in comments + if (!line.trim().startsWith("//") && !line.trim().startsWith("*")) { + // Ignore empty strings in template literals + if (!/`[^`]*\$\{[^}]*\}[^`]*`/.test(line)) { + violations.push({ + file: currentFile, + line: headLine, + content: line.substring(1).trim(), + }); + } + } + } + headLine++; + } else if (!line.startsWith("-")) { + headLine++; + } + }); + + return violations; +} +main().catch((error) => { + core.setFailed(`Error running empty string check: ${error instanceof Error ? error.message : String(error)}`); +}); diff --git a/.github/knip.ts b/.github/knip.ts new file mode 100644 index 0000000..25f5e28 --- /dev/null +++ b/.github/knip.ts @@ -0,0 +1,24 @@ +import type { KnipConfig } from "knip"; + +const config: KnipConfig = { + entry: ["build/index.ts", ".github/empty-string-checker.ts"], + project: ["src/**/*.ts"], + ignore: ["src/types/config.ts", "**/__mocks__/**", "**/__fixtures__/**"], + ignoreExportsUsedInFile: true, + // eslint can also be safely ignored as per the docs: https://knip.dev/guides/handling-issues#eslint--jest + ignoreDependencies: [ + "eslint-config-prettier", + "eslint-plugin-prettier", + "@types/jest", + "@mswjs/data", + "@coinbase/wallet-sdk", + "@reown/appkit", + "@reown/appkit-adapter-ethers5", + "ethers", + "@types/react", + "react", + ], + eslint: true, +}; + +export default config; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1bcdf25..28d7188 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,8 +15,6 @@ jobs: steps: - name: Check out repository uses: actions/checkout@v4 - # with: - # submodules: "recursive" # Ensures submodules are checked out - name: Set up Node.js uses: actions/setup-node@v4 @@ -27,10 +25,6 @@ jobs: run: | yarn yarn build - env: # Set environment variables for the build - FRONTEND_URL: "https://onboard.ubq.fi" - SUPABASE_URL: "https://wfzpewmlyiozupulbuur.supabase.co" - SUPABASE_ANON_KEY: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndmenBld21seWlvenVwdWxidXVyIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTU2NzQzMzksImV4cCI6MjAxMTI1MDMzOX0.SKIL3Q0NOBaMehH0ekFspwgcu3afp3Dl9EDzPqs1nKs" - name: Upload build artifact uses: actions/upload-artifact@v4 diff --git a/.github/workflows/conventional-commits.yml b/.github/workflows/conventional-commits.yml index 6b3066e..8d17568 100644 --- a/.github/workflows/conventional-commits.yml +++ b/.github/workflows/conventional-commits.yml @@ -8,5 +8,5 @@ jobs: name: Conventional Commits runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ubiquity/action-conventional-commits@master diff --git a/.github/workflows/cypress-testing.yml b/.github/workflows/cypress-testing.yml index 5e60c05..552e8f0 100644 --- a/.github/workflows/cypress-testing.yml +++ b/.github/workflows/cypress-testing.yml @@ -1,7 +1,10 @@ name: Run Cypress testing suite on: workflow_dispatch: - pull_request: + workflow_run: + workflows: ["Build"] + types: + - completed jobs: cypress-run: @@ -17,13 +20,9 @@ jobs: with: build: yarn run build start: yarn start - browser: chrome env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - FRONTEND_URL: "http://localhost:8080" - SUPABASE_URL: "https://wfzpewmlyiozupulbuur.supabase.co" - SUPABASE_ANON_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndmenBld21seWlvenVwdWxidXVyIiwicm9sZSI6ImFub24iLCJpYXQiOjE2OTU2NzQzMzksImV4cCI6MjAxMTI1MDMzOX0.SKIL3Q0NOBaMehH0ekFspwgcu3afp3Dl9EDzPqs1nKs - uses: actions/upload-artifact@v4 if: failure() with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2d51792..3d6b9a8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,3 +24,5 @@ jobs: cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }} commit_sha: ${{ github.event.workflow_run.head_sha }} workflow_run_id: ${{ github.event.workflow_run.id }} + app_id: ${{ secrets.APP_ID }} + app_private_key: ${{ secrets.APP_PRIVATE_KEY }} diff --git a/.github/workflows/jest-testing.yml b/.github/workflows/jest-testing.yml new file mode 100644 index 0000000..7f8747e --- /dev/null +++ b/.github/workflows/jest-testing.yml @@ -0,0 +1,27 @@ +name: Run Jest testing suite +on: + workflow_dispatch: + pull_request: + +env: + NODE_ENV: "test" + +jobs: + testing: + permissions: write-all + runs-on: ubuntu-latest + steps: + - uses: actions/setup-node@v4 + with: + node-version: "20.10.0" + + - uses: actions/checkout@master + with: + fetch-depth: 0 + + - name: Jest With Coverage + run: yarn install --immutable --immutable-cache --check-cache && yarn test + + - name: Add Jest Report to Summary + if: always() + run: echo "$(cat test-dashboard.md)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/knip-reporter.yml b/.github/workflows/knip-reporter.yml index 1b0be66..282c9a8 100644 --- a/.github/workflows/knip-reporter.yml +++ b/.github/workflows/knip-reporter.yml @@ -11,17 +11,6 @@ jobs: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion != 'success' }} steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20.10.0 - - - name: Install toolchain - run: yarn install - - uses: actions/download-artifact@v4 with: name: knip-results diff --git a/.github/workflows/knip.yml b/.github/workflows/knip.yml index 90f47e5..809976b 100644 --- a/.github/workflows/knip.yml +++ b/.github/workflows/knip.yml @@ -10,6 +10,10 @@ jobs: - name: Checkout uses: actions/checkout@v4 + # needed to use yarn v4 + - name: Enable corepack + run: corepack enable + - name: Setup Node uses: actions/setup-node@v4 with: @@ -22,7 +26,7 @@ jobs: run: echo ${{ github.event.number }} > pr-number.txt - name: Run Knip - run: yarn knip || yarn -s knip --reporter json > knip-results.json + run: yarn knip || yarn knip --reporter json > knip-results.json - name: Upload knip result if: failure() diff --git a/.github/workflows/no-empty-strings.yml b/.github/workflows/no-empty-strings.yml new file mode 100644 index 0000000..e314106 --- /dev/null +++ b/.github/workflows/no-empty-strings.yml @@ -0,0 +1,32 @@ +name: Empty String Check + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + check-for-empty-strings: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20.10.0" + - name: Get GitHub App token + uses: actions/create-github-app-token + id: get_app_token + with: + app_id: ${{ secrets.APP_ID }} + private_key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Install Dependencies + run: | + yarn add tsx simple-git + - name: Check for Empty Strings + run: | + yarn tsx .github/empty-string-checker.ts + env: + GITHUB_TOKEN: ${{ steps.get_app_token.outputs.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_BASE_REF: ${{ github.base_ref }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..a38ccee --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,20 @@ +name: release-please + +on: + workflow_dispatch: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + release-type: simple + target-branch: main diff --git a/.gitignore b/.gitignore index 7bad22f..417a7b3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ node_modules .pnp.loader.mjs .env static/dist -metamask -static/out \ No newline at end of file +coverage +junit.xml +cypress/screenshots diff --git a/README.md b/README.md index 108ad82..caace40 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,70 @@ -# `@ubiquity/onboard.ubq.fi` +# `@ubiquity/ts-template` -Generates the configuration for organizations, by creating a default configuration and creating a repository under the -given Organization. +This template repository includes support for the following: -## Requirements +- TypeScript +- Environment Variables +- Conventional Commits +- Automatic deployment to Cloudflare Pages -Copy the `env.example` to `.env` and fill the required variables. +## Testing + +### Cypress -## Run +To test with Cypress Studio UI, run ```shell -yarn start +yarn cy:open ``` -Should make the front-end page available at [http://localhost:8080](http://localhost:8080). - -### Required fields +Otherwise, to simply run the tests through the console, run -- `CHAIN_ID`: the id of the network you want to use for the transactions -- `WALLET_PRIVATE_KEY`: the [64 digits](https://www.browserling.com/tools/random-hex) crypto wallet key that will be used for transactions -- `GITHUB_PAT`: a [GitHub Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) -- `ORG_NAME`: the name of the [organization](https://github.com/settings/organizations) where you want to add the bot +```shell +yarn cy:run +``` -## Testing +### Jest -To test with Cypress Studio UI, run +To start Jest tests, run ```shell -yarn cy:open +yarn test ``` -Otherwise to simply run the tests through the console, run +## Sync any repository to latest `ts-template` -```shell -yarn cy:run -``` +A bash function that can do this for you: + +```bash +sync-branch-to-template() { + local branch_name + branch_name=$(git rev-parse --abbrev-ref HEAD) + local original_remote + original_remote=$(git remote show | head -n 1) + + # Add the template remote + git remote add template https://github.com/ubiquity/ts-template + + # Fetch from the template remote + git fetch template development -To test in a real-world scenario, you will need to create an Organization under your GitHub account, and use it as a -dummy. If the operation is successful, you will see a new repository appear with the Ubiquibot configuration. + if [ "$branch_name" != "HEAD" ]; then + # Create a new branch and switch to it + git checkout -b "chore/merge-${branch_name}-template" + + # Merge the changes from the template remote + git merge template/development --allow-unrelated-histories + + # Switch back to the original branch + git checkout "$branch_name" + + # Push the changes to the original remote + git push "$original_remote" HEAD:"$branch_name" + else + echo "You are in a detached HEAD state. Please checkout a branch first." + fi + + # Remove the template remote + # git remote remove template +} +``` diff --git a/build/esbuild-build.ts b/build/esbuild-build.ts index c88a5ff..21e3f06 100644 --- a/build/esbuild-build.ts +++ b/build/esbuild-build.ts @@ -1,55 +1,49 @@ -import { execSync } from "child_process"; import { config } from "dotenv"; -import esbuild from "esbuild"; +import esbuild, { BuildOptions } from "esbuild"; +config(); -const typescriptEntries = ["static/scripts/onboarding/onboarding.ts"]; -const cssEntries = ["static/styles/onboarding/onboarding.css"]; -export const entries = [...typescriptEntries, ...cssEntries]; +const ENTRY_POINTS = { + typescript: ["static/main.ts"], + // css: ["static/style.css"], +}; + +const DATA_URL_LOADERS = [".png", ".woff", ".woff2", ".eot", ".ttf", ".svg"]; -export const esBuildContext: esbuild.BuildOptions = { +export const esbuildOptions: BuildOptions = { sourcemap: true, - entryPoints: entries, + entryPoints: [...ENTRY_POINTS.typescript /* ...ENTRY_POINTS.css */], bundle: true, minify: false, - loader: { - ".png": "dataurl", - ".woff": "dataurl", - ".woff2": "dataurl", - ".eot": "dataurl", - ".ttf": "dataurl", - ".svg": "dataurl", - }, - outdir: "static/out", - define: createEnvDefines(["SUPABASE_URL", "SUPABASE_ANON_KEY", "FRONTEND_URL"], { - commitHash: execSync(`git rev-parse --short HEAD`).toString().trim(), - }), + loader: Object.fromEntries(DATA_URL_LOADERS.map((ext) => [ext, "dataurl"])), + outdir: "static/dist", }; -esbuild - .build(esBuildContext) - .then(() => { +async function runBuild() { + try { + await esbuild.build(esbuildOptions); console.log("\tesbuild complete"); - }) - .catch((err) => { + } catch (err) { console.error(err); process.exit(1); - }); - -function createEnvDefines(environmentVariables: string[], generatedAtBuild: Record): Record { - const defines: Record = {}; - config(); - for (const name of environmentVariables) { - const envVar = process.env[name]; - if (envVar !== undefined) { - defines[name] = JSON.stringify(envVar); - } else { - throw new Error(`Missing environment variable: ${name}`); - } } - Object.keys(generatedAtBuild).forEach((key) => { - if (Object.prototype.hasOwnProperty.call(generatedAtBuild, key)) { - defines[key] = JSON.stringify(generatedAtBuild[key]); - } - }); - return defines; } + +void runBuild(); + +// function createEnvDefines(environmentVariables: string[], generatedAtBuild: Record): Record { +// const defines: Record = {}; +// for (const name of environmentVariables) { +// const envVar = process.env[name]; +// if (envVar !== undefined) { +// defines[name] = JSON.stringify(envVar); +// } else { +// throw new Error(`Missing environment variable: ${name}`); +// } +// } +// for (const key in generatedAtBuild) { +// if (Object.prototype.hasOwnProperty.call(generatedAtBuild, key)) { +// defines[key] = JSON.stringify(generatedAtBuild[key]); +// } +// } +// return defines; +// } diff --git a/build/esbuild-server.ts b/build/esbuild-server.ts index bb992ae..0ba3c3f 100644 --- a/build/esbuild-server.ts +++ b/build/esbuild-server.ts @@ -1,18 +1,20 @@ import esbuild from "esbuild"; -import { esBuildContext } from "./esbuild-build"; +import { esbuildOptions } from "./esbuild-build"; -(async () => { - await server(); -})().catch((error) => { - console.error("Unhandled error:", error); - process.exit(1); -}); +void (async () => { + try { + await server(); + } catch (error) { + console.error("Unhandled error:", error); + process.exit(1); + } +})(); export async function server() { - const _context = await esbuild.context(esBuildContext); - const { port } = await _context.serve({ + const context = await esbuild.context(esbuildOptions); + const { host, port } = await context.serve({ servedir: "static", port: 8080, }); - console.log(`http://localhost:${port}`); + console.log(`Server running at http://${host}:${port}`); } diff --git a/cypress.config.ts b/cypress.config.ts index 9674920..7272f26 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,24 +1,11 @@ import { defineConfig } from "cypress"; -import path from "node:path"; -import AdmZip from "adm-zip"; export default defineConfig({ e2e: { - setupNodeEvents(on) { - on("before:browser:launch", (browser, launchOptions) => { - // absolute path to the unpacked extension's folder - // NOTE: extensions cannot be loaded in headless Chrome - const extensionPath = path.resolve(__dirname, "./cypress/fixtures"); - const unzippedPath = `${extensionPath}/metamask`; - const zip = new AdmZip(`${extensionPath}/metamask-chrome.zip`); - zip.extractAllTo(unzippedPath); - launchOptions.extensions.push(unzippedPath); - - return launchOptions; - }); + setupNodeEvents() { + // implement node event listeners here }, experimentalStudio: true, baseUrl: "http://localhost:8080", - watchForFileChanges: false, }, }); diff --git a/cypress/e2e/main.cy.ts b/cypress/e2e/main.cy.ts index b423ce3..81df8c5 100644 --- a/cypress/e2e/main.cy.ts +++ b/cypress/e2e/main.cy.ts @@ -1,70 +1,4 @@ -import { OAuthToken } from "../../static/scripts/onboarding/github-login-button"; - describe("Homepage tests", () => { - const ORG_NAME = "Ubiquity"; - let loginToken: OAuthToken; - - beforeEach(() => { - cy.fixture("get-user.json").then((file) => { - cy.intercept("GET", `https://api.github.com/users/${ORG_NAME}`, (req) => { - req.reply(file); - }).as("githubGetUser"); - }); - cy.fixture("get-ubiquibot-config.json").then((file) => { - cy.intercept("GET", `https://api.github.com/repos/${ORG_NAME}/ubiquibot-config`, (req) => { - req.reply(file); - }).as("githubGetUbiquibotConfig"); - }); - cy.fixture("get-repos.json").then((file) => { - cy.intercept("GET", `https://api.github.com/orgs/${ORG_NAME}/repos`, (req) => { - req.reply(file); - }).as("githubGetRepos"); - }); - cy.fixture("get-installations.json").then((file) => { - cy.intercept("GET", `https://api.github.com/orgs/${ORG_NAME}/installations**`, (req) => { - req.reply(file); - }).as("githubGetInstallations"); - }); - cy.fixture("get-installation-repositories.json").then((file) => { - cy.intercept("GET", `https://api.github.com/user/installations/47252474/repositories`, (req) => { - req.reply(file); - }).as("githubGetInstallationRepositories"); - }); - cy.fixture("put-file.json").then((file) => { - cy.intercept("PUT", `https://api.github.com/user/installations/47252474/repositories/641336624`, (req) => { - req.reply(file); - }).as("githubPutInstallation"); - }); - cy.fixture("put-file.json").then((file) => { - cy.intercept("PUT", `https://api.github.com/repos/${ORG_NAME}/ubiquibot-config/contents/.github%2Fubiquibot-config.yml`, (req) => { - req.reply(file); - }).as("githubPutConfigFile"); - }); - cy.fixture("get-orgs.json").then((file) => { - cy.intercept("GET", `https://api.github.com/user/orgs**`, (req) => { - req.reply(file); - }).as("githubGetUserOrgs"); - }); - cy.fixture("get-org-installations.json").then((file) => { - cy.intercept("GET", `https://api.github.com/orgs/${ORG_NAME.toLowerCase()}/installations**`, (req) => { - req.reply(file); - }).as("githubGetOrgInstallations"); - }); - cy.fixture("get-search.json").then((file) => { - cy.intercept("GET", `https://api.github.com/search/repositories**`, (req) => { - req.reply(file); - }).as("githubSearch"); - }); - cy.fixture("put-config.json").then((file) => { - cy.intercept("PUT", `https://api.github.com/repos/${ORG_NAME.toLowerCase()}/ubiquibot-config/contents/.github**`, (req) => { - req.reply(file); - }).as("githubPutContents"); - }); - cy.fixture("user-token.json").then((content) => { - loginToken = content; - }); - }); - it("Console is cleared of errors and warnings", () => { cy.visit("/", { onBeforeLoad(win) { @@ -73,31 +7,6 @@ describe("Homepage tests", () => { }); cy.get("@consoleError").should("not.be.called"); cy.get("body").should("exist"); - }); - - it.only("Create onboarding repository", () => { - cy.visit("/"); - cy.intercept("https://github.com/login/oauth/authorize**", (req) => { - req.reply({ - statusCode: 200, - }); - // Simulate login token - // cSpell: ignore wfzpewmlyiozupulbuur - window.localStorage.setItem("sb-wfzpewmlyiozupulbuur-auth-token", JSON.stringify(loginToken)); - }).as("githubLogin"); - cy.get("#github-login-button").click(); - cy.visit("/"); - cy.wait("@githubGetUserOrgs"); - cy.get("#setBtn").click(); - cy.log("Display warning on empty WALLET_PRIVATE_KEY"); - cy.get(":nth-child(3) > .status-log.warn").contains(/.+/); - cy.get("#walletPrivateKey").type("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); - cy.get("#orgName").select("ubiquity"); - cy.get("#setBtn").click(); - cy.get("#outKey").then((e) => { - expect(e.val()).not.to.be.empty; - }); - cy.log("Expected to be a step 2 of the form"); - cy.get("#stepper > :nth-child(2)").should("have.class", "active"); + cy.get("h1").should("exist"); }); }); diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/fixtures/get-installation-repositories.json b/cypress/fixtures/get-installation-repositories.json deleted file mode 100644 index c5922ce..0000000 --- a/cypress/fixtures/get-installation-repositories.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "total_count": 1, - "repositories": [ - { - "id": 767829567, - "node_id": "R_kgDOLcQmPw", - "name": "ubiquibot-config", - "full_name": "Ubiquity/ubiquibot-config", - "private": true, - "owner": { - "login": "Ubiquity", - "id": 159901852, - "node_id": "O_kgDOCYfonA", - "avatar_url": "https://avatars.githubusercontent.com/u/159901852?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Ubiquity", - "html_url": "https://github.com/Ubiquity", - "followers_url": "https://api.github.com/users/Ubiquity/followers", - "following_url": "https://api.github.com/users/Ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/Ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/Ubiquity/orgs", - "repos_url": "https://api.github.com/users/Ubiquity/repos", - "events_url": "https://api.github.com/users/Ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/Ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/Ubiquity/ubiquibot-config", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/Ubiquity/ubiquibot-config", - "forks_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/forks", - "keys_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/teams", - "hooks_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/hooks", - "issue_events_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/issues/events{/number}", - "events_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/events", - "assignees_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/assignees{/user}", - "branches_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/branches{/branch}", - "tags_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/tags", - "blobs_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/statuses/{sha}", - "languages_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/languages", - "stargazers_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/stargazers", - "contributors_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/contributors", - "subscribers_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/subscribers", - "subscription_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/subscription", - "commits_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/contents/{+path}", - "compare_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/merges", - "archive_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/downloads", - "issues_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/issues{/number}", - "pulls_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/pulls{/number}", - "milestones_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/milestones{/number}", - "notifications_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/labels{/name}", - "releases_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/releases{/id}", - "deployments_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/deployments", - "created_at": "2024-03-06T01:00:06Z", - "updated_at": "2024-03-06T01:00:07Z", - "pushed_at": "2024-03-06T01:00:07Z", - "git_url": "git://github.com/Ubiquity/ubiquibot-config.git", - "ssh_url": "git@github.com:Ubiquity/ubiquibot-config.git", - "clone_url": "https://github.com/Ubiquity/ubiquibot-config.git", - "svn_url": "https://github.com/Ubiquity/ubiquibot-config", - "homepage": null, - "size": 0, - "stargazers_count": 0, - "watchers_count": 0, - "language": null, - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": false, - "has_pages": false, - "has_discussions": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 0, - "license": null, - "allow_forking": false, - "is_template": false, - "web_commit_signoff_required": false, - "topics": [], - "visibility": "private", - "forks": 0, - "open_issues": 0, - "watchers": 0, - "default_branch": "main", - "permissions": { - "admin": true, - "maintain": true, - "push": true, - "triage": true, - "pull": true - } - } - ] -} diff --git a/cypress/fixtures/get-installations.json b/cypress/fixtures/get-installations.json deleted file mode 100644 index 9c9c2f3..0000000 --- a/cypress/fixtures/get-installations.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "total_count": 2, - "installations": [ - { - "id": 47252474, - "account": { - "login": "Ubiquity", - "id": 159901852, - "node_id": "O_kgDOCYfonA", - "avatar_url": "https://avatars.githubusercontent.com/u/159901852?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Ubiquity", - "html_url": "https://github.com/Ubiquity", - "followers_url": "https://api.github.com/users/Ubiquity/followers", - "following_url": "https://api.github.com/users/Ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/Ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/Ubiquity/orgs", - "repos_url": "https://api.github.com/users/Ubiquity/repos", - "events_url": "https://api.github.com/users/Ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/Ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "repository_selection": "all", - "access_tokens_url": "https://api.github.com/app/installations/47252474/access_tokens", - "repositories_url": "https://api.github.com/installation/repositories", - "html_url": "https://github.com/organizations/Ubiquity/settings/installations/47252474", - "app_id": 236521, - "app_slug": "ubiquibot", - "target_id": 159901852, - "target_type": "Organization", - "permissions": { - "issues": "write", - "actions": "write", - "members": "read", - "contents": "write", - "metadata": "read", - "pull_requests": "write" - }, - "events": [ - "commit_comment", - "create", - "delete", - "fork", - "gollum", - "issues", - "issue_comment", - "label", - "member", - "membership", - "merge_queue_entry", - "milestone", - "organization", - "public", - "pull_request", - "pull_request_review", - "pull_request_review_comment", - "pull_request_review_thread", - "push", - "release", - "repository", - "repository_dispatch", - "star", - "team", - "team_add", - "watch", - "workflow_dispatch", - "workflow_job", - "workflow_run" - ], - "created_at": "2024-02-13T19:37:30.000+09:00", - "updated_at": "2024-02-13T19:37:31.000+09:00", - "single_file_name": null, - "has_multiple_single_files": false, - "single_file_paths": [], - "suspended_by": null, - "suspended_at": null - }, - { - "id": 47255717, - "account": { - "login": "Ubiquity", - "id": 159901852, - "node_id": "O_kgDOCYfonA", - "avatar_url": "https://avatars.githubusercontent.com/u/159901852?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Ubiquity", - "html_url": "https://github.com/Ubiquity", - "followers_url": "https://api.github.com/users/Ubiquity/followers", - "following_url": "https://api.github.com/users/Ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/Ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/Ubiquity/orgs", - "repos_url": "https://api.github.com/users/Ubiquity/repos", - "events_url": "https://api.github.com/users/Ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/Ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "repository_selection": "selected", - "access_tokens_url": "https://api.github.com/app/installations/47255717/access_tokens", - "repositories_url": "https://api.github.com/installation/repositories", - "html_url": "https://github.com/organizations/Ubiquity/settings/installations/47255717", - "app_id": 827286, - "app_slug": "ubi", - "target_id": 159901852, - "target_type": "Organization", - "permissions": {}, - "events": [], - "created_at": "2024-02-13T21:12:10.000+09:00", - "updated_at": "2024-02-13T21:12:10.000+09:00", - "single_file_name": null, - "has_multiple_single_files": false, - "single_file_paths": [], - "suspended_by": null, - "suspended_at": null - } - ] -} diff --git a/cypress/fixtures/get-org-installations.json b/cypress/fixtures/get-org-installations.json deleted file mode 100644 index dba3ee1..0000000 --- a/cypress/fixtures/get-org-installations.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "total_count": 1, - "installations": [ - { - "id": 47252474, - "account": { - "login": "ubiquity", - "id": 159901852, - "node_id": "O_kgDOCYfonA", - "avatar_url": "https://avatars.githubusercontent.com/u/159901852?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ubiquity", - "html_url": "https://github.com/ubiquity", - "followers_url": "https://api.github.com/users/ubiquity/followers", - "following_url": "https://api.github.com/users/ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/ubiquity/orgs", - "repos_url": "https://api.github.com/users/ubiquity/repos", - "events_url": "https://api.github.com/users/ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "repository_selection": "selected", - "access_tokens_url": "https://api.github.com/app/installations/47252474/access_tokens", - "repositories_url": "https://api.github.com/installation/repositories", - "html_url": "https://github.com/organizations/ubiquity/settings/installations/47252474", - "app_id": 236521, - "app_slug": "ubiquibot", - "target_id": 159901852, - "target_type": "Organization", - "permissions": { - "issues": "write", - "actions": "write", - "members": "read", - "contents": "write", - "metadata": "read", - "pull_requests": "write" - }, - "events": [ - "commit_comment", - "create", - "delete", - "fork", - "gollum", - "issues", - "issue_comment", - "label", - "member", - "membership", - "merge_queue_entry", - "milestone", - "organization", - "public", - "pull_request", - "pull_request_review", - "pull_request_review_comment", - "pull_request_review_thread", - "push", - "release", - "repository", - "repository_dispatch", - "star", - "team", - "team_add", - "watch", - "workflow_dispatch", - "workflow_job", - "workflow_run" - ], - "created_at": "2024-02-13T19:37:30.000+09:00", - "updated_at": "2024-03-11T20:40:33.000+09:00", - "single_file_name": null, - "has_multiple_single_files": false, - "single_file_paths": [], - "suspended_by": null, - "suspended_at": null - } - ] -} diff --git a/cypress/fixtures/get-orgs.json b/cypress/fixtures/get-orgs.json deleted file mode 100644 index 4d1b47b..0000000 --- a/cypress/fixtures/get-orgs.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "login": "ubiquity2", - "id": 76412718, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjc2NDEyNzE4", - "url": "https://api.github.com/orgs/ubiquity2", - "repos_url": "https://api.github.com/orgs/ubiquity2/repos", - "events_url": "https://api.github.com/orgs/ubiquity2/events", - "hooks_url": "https://api.github.com/orgs/ubiquity2/hooks", - "issues_url": "https://api.github.com/orgs/ubiquity2/issues", - "members_url": "https://api.github.com/orgs/ubiquity2/members{/member}", - "public_members_url": "https://api.github.com/orgs/ubiquity2/public_members{/member}", - "avatar_url": "https://avatars.githubusercontent.com/u/76412717?v=4", - "description": "The Metaverse Bank 2." - }, - { - "login": "ubiquity", - "id": 76412717, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjc2NDEyNzE3", - "url": "https://api.github.com/orgs/ubiquity", - "repos_url": "https://api.github.com/orgs/ubiquity/repos", - "events_url": "https://api.github.com/orgs/ubiquity/events", - "hooks_url": "https://api.github.com/orgs/ubiquity/hooks", - "issues_url": "https://api.github.com/orgs/ubiquity/issues", - "members_url": "https://api.github.com/orgs/ubiquity/members{/member}", - "public_members_url": "https://api.github.com/orgs/ubiquity/public_members{/member}", - "avatar_url": "https://avatars.githubusercontent.com/u/76412717?v=4", - "description": "The Metaverse Bank." - } -] diff --git a/cypress/fixtures/get-repos.json b/cypress/fixtures/get-repos.json deleted file mode 100644 index c885ca8..0000000 --- a/cypress/fixtures/get-repos.json +++ /dev/null @@ -1,141 +0,0 @@ -{ - "id": 767829567, - "node_id": "R_kgDOLcQmPw", - "name": "ubiquibot-config", - "full_name": "Ubiquity/ubiquibot-config", - "private": true, - "owner": { - "login": "Ubiquity", - "id": 159901852, - "node_id": "O_kgDOCYfonA", - "avatar_url": "https://avatars.githubusercontent.com/u/159901852?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Ubiquity", - "html_url": "https://github.com/Ubiquity", - "followers_url": "https://api.github.com/users/Ubiquity/followers", - "following_url": "https://api.github.com/users/Ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/Ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/Ubiquity/orgs", - "repos_url": "https://api.github.com/users/Ubiquity/repos", - "events_url": "https://api.github.com/users/Ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/Ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/Ubiquity/ubiquibot-config", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/Ubiquity/ubiquibot-config", - "forks_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/forks", - "keys_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/teams", - "hooks_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/hooks", - "issue_events_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/issues/events{/number}", - "events_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/events", - "assignees_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/assignees{/user}", - "branches_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/branches{/branch}", - "tags_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/tags", - "blobs_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/statuses/{sha}", - "languages_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/languages", - "stargazers_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/stargazers", - "contributors_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/contributors", - "subscribers_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/subscribers", - "subscription_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/subscription", - "commits_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/contents/{+path}", - "compare_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/merges", - "archive_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/downloads", - "issues_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/issues{/number}", - "pulls_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/pulls{/number}", - "milestones_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/milestones{/number}", - "notifications_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/labels{/name}", - "releases_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/releases{/id}", - "deployments_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/deployments", - "created_at": "2024-03-06T01:00:06Z", - "updated_at": "2024-03-06T01:00:07Z", - "pushed_at": "2024-03-06T01:00:07Z", - "git_url": "git://github.com/Ubiquity/ubiquibot-config.git", - "ssh_url": "git@github.com:Ubiquity/ubiquibot-config.git", - "clone_url": "https://github.com/Ubiquity/ubiquibot-config.git", - "svn_url": "https://github.com/Ubiquity/ubiquibot-config", - "homepage": null, - "size": 0, - "stargazers_count": 0, - "watchers_count": 0, - "language": null, - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": false, - "has_pages": false, - "has_discussions": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 0, - "license": null, - "allow_forking": false, - "is_template": false, - "web_commit_signoff_required": false, - "topics": [], - "visibility": "private", - "forks": 0, - "open_issues": 0, - "watchers": 0, - "default_branch": "main", - "permissions": { - "admin": true, - "maintain": true, - "push": true, - "triage": true, - "pull": true - }, - "allow_squash_merge": true, - "allow_merge_commit": true, - "allow_rebase_merge": true, - "allow_auto_merge": false, - "delete_branch_on_merge": false, - "allow_update_branch": false, - "use_squash_pr_title_as_default": false, - "squash_merge_commit_message": "COMMIT_MESSAGES", - "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", - "merge_commit_message": "PR_TITLE", - "merge_commit_title": "MERGE_MESSAGE", - "custom_properties": {}, - "organization": { - "login": "Ubiquity", - "id": 159901852, - "node_id": "O_kgDOCYfonA", - "avatar_url": "https://avatars.githubusercontent.com/u/159901852?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/Ubiquity", - "html_url": "https://github.com/Ubiquity", - "followers_url": "https://api.github.com/users/Ubiquity/followers", - "following_url": "https://api.github.com/users/Ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/Ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/Ubiquity/orgs", - "repos_url": "https://api.github.com/users/Ubiquity/repos", - "events_url": "https://api.github.com/users/Ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/Ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "network_count": 0, - "subscribers_count": 0 -} diff --git a/cypress/fixtures/get-search.json b/cypress/fixtures/get-search.json deleted file mode 100644 index 71262b7..0000000 --- a/cypress/fixtures/get-search.json +++ /dev/null @@ -1,114 +0,0 @@ -{ - "total_count": 1, - "incomplete_results": false, - "items": [ - { - "id": 770341621, - "node_id": "R_kgDOLep69Q", - "name": "ubiquibot-config", - "full_name": "ubiquity/ubiquibot-config", - "private": true, - "owner": { - "login": "ubiquity", - "id": 159901852, - "node_id": "O_kgDOCYfonA", - "avatar_url": "https://avatars.githubusercontent.com/u/159901852?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ubiquity", - "html_url": "https://github.com/ubiquity", - "followers_url": "https://api.github.com/users/ubiquity/followers", - "following_url": "https://api.github.com/users/ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/ubiquity/orgs", - "repos_url": "https://api.github.com/users/ubiquity/repos", - "events_url": "https://api.github.com/users/ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/ubiquity/ubiquibot-config", - "description": null, - "fork": false, - "url": "https://api.github.com/repos/ubiquity/ubiquibot-config", - "forks_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/forks", - "keys_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/teams", - "hooks_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/hooks", - "issue_events_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/issues/events{/number}", - "events_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/events", - "assignees_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/assignees{/user}", - "branches_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/branches{/branch}", - "tags_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/tags", - "blobs_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/statuses/{sha}", - "languages_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/languages", - "stargazers_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/stargazers", - "contributors_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/contributors", - "subscribers_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/subscribers", - "subscription_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/subscription", - "commits_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/contents/{+path}", - "compare_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/merges", - "archive_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/downloads", - "issues_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/issues{/number}", - "pulls_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/pulls{/number}", - "milestones_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/milestones{/number}", - "notifications_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/labels{/name}", - "releases_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/releases{/id}", - "deployments_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/deployments", - "created_at": "2024-03-11T11:39:17Z", - "updated_at": "2024-03-11T11:39:17Z", - "pushed_at": "2024-03-11T11:39:18Z", - "git_url": "git://github.com/ubiquity/ubiquibot-config.git", - "ssh_url": "git@github.com:ubiquity/ubiquibot-config.git", - "clone_url": "https://github.com/ubiquity/ubiquibot-config.git", - "svn_url": "https://github.com/ubiquity/ubiquibot-config", - "homepage": null, - "size": 2, - "stargazers_count": 0, - "watchers_count": 0, - "language": null, - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": false, - "has_pages": false, - "has_discussions": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 0, - "license": null, - "allow_forking": false, - "is_template": false, - "web_commit_signoff_required": false, - "topics": [], - "visibility": "private", - "forks": 0, - "open_issues": 0, - "watchers": 0, - "default_branch": "main", - "permissions": { - "admin": true, - "maintain": true, - "push": true, - "triage": true, - "pull": true - }, - "score": 1.0 - } - ] -} diff --git a/cypress/fixtures/get-ubiquibot-config.json b/cypress/fixtures/get-ubiquibot-config.json deleted file mode 100644 index 5c3ce87..0000000 --- a/cypress/fixtures/get-ubiquibot-config.json +++ /dev/null @@ -1,142 +0,0 @@ -{ - "id": 641336624, - "node_id": "R_kgDOJjoFMA", - "name": "ubiquibot-config", - "full_name": "ubiquity/ubiquibot-config", - "private": true, - "owner": { - "login": "ubiquity", - "id": 76412717, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjc2NDEyNzE3", - "avatar_url": "https://avatars.githubusercontent.com/u/76412717?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ubiquity", - "html_url": "https://github.com/ubiquity", - "followers_url": "https://api.github.com/users/ubiquity/followers", - "following_url": "https://api.github.com/users/ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/ubiquity/orgs", - "repos_url": "https://api.github.com/users/ubiquity/repos", - "events_url": "https://api.github.com/users/ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "html_url": "https://github.com/ubiquity/ubiquibot-config", - "description": "The organization-wide default UbiquiBot configuration.", - "fork": false, - "url": "https://api.github.com/repos/ubiquity/ubiquibot-config", - "forks_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/forks", - "keys_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/teams", - "hooks_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/hooks", - "issue_events_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/issues/events{/number}", - "events_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/events", - "assignees_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/assignees{/user}", - "branches_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/branches{/branch}", - "tags_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/tags", - "blobs_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/statuses/{sha}", - "languages_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/languages", - "stargazers_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/stargazers", - "contributors_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/contributors", - "subscribers_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/subscribers", - "subscription_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/subscription", - "commits_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/contents/{+path}", - "compare_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/merges", - "archive_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/downloads", - "issues_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/issues{/number}", - "pulls_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/pulls{/number}", - "milestones_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/milestones{/number}", - "notifications_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/labels{/name}", - "releases_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/releases{/id}", - "deployments_url": "https://api.github.com/repos/ubiquity/ubiquibot-config/deployments", - "created_at": "2023-05-16T09:05:13Z", - "updated_at": "2023-08-27T05:04:55Z", - "pushed_at": "2024-02-02T10:21:15Z", - "git_url": "git://github.com/ubiquity/ubiquibot-config.git", - "ssh_url": "git@github.com:ubiquity/ubiquibot-config.git", - "clone_url": "https://github.com/ubiquity/ubiquibot-config.git", - "svn_url": "https://github.com/ubiquity/ubiquibot-config", - "homepage": "", - "size": 46, - "stargazers_count": 0, - "watchers_count": 0, - "language": null, - "has_issues": true, - "has_projects": true, - "has_downloads": true, - "has_wiki": false, - "has_pages": false, - "has_discussions": false, - "forks_count": 0, - "mirror_url": null, - "archived": false, - "disabled": false, - "open_issues_count": 0, - "license": null, - "allow_forking": true, - "is_template": false, - "web_commit_signoff_required": false, - "topics": [], - "visibility": "private", - "forks": 0, - "open_issues": 0, - "watchers": 0, - "default_branch": "development", - "permissions": { - "admin": false, - "maintain": false, - "push": true, - "triage": true, - "pull": true - }, - "temp_clone_token": "ACK2JIAP6BCCDFY4KMHIIHDF43KQE", - "allow_squash_merge": true, - "allow_merge_commit": true, - "allow_rebase_merge": true, - "allow_auto_merge": false, - "delete_branch_on_merge": false, - "allow_update_branch": false, - "use_squash_pr_title_as_default": false, - "squash_merge_commit_message": "COMMIT_MESSAGES", - "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", - "merge_commit_message": "PR_TITLE", - "merge_commit_title": "MERGE_MESSAGE", - "custom_properties": {}, - "organization": { - "login": "ubiquity", - "id": 76412717, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjc2NDEyNzE3", - "avatar_url": "https://avatars.githubusercontent.com/u/76412717?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ubiquity", - "html_url": "https://github.com/ubiquity", - "followers_url": "https://api.github.com/users/ubiquity/followers", - "following_url": "https://api.github.com/users/ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/ubiquity/orgs", - "repos_url": "https://api.github.com/users/ubiquity/repos", - "events_url": "https://api.github.com/users/ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "network_count": 0, - "subscribers_count": 1 -} diff --git a/cypress/fixtures/get-user.json b/cypress/fixtures/get-user.json deleted file mode 100644 index 4a33ca0..0000000 --- a/cypress/fixtures/get-user.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "login": "ubiquity", - "id": 76412717, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjc2NDEyNzE3", - "avatar_url": "https://avatars.githubusercontent.com/u/76412717?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ubiquity", - "html_url": "https://github.com/ubiquity", - "followers_url": "https://api.github.com/users/ubiquity/followers", - "following_url": "https://api.github.com/users/ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/ubiquity/orgs", - "repos_url": "https://api.github.com/users/ubiquity/repos", - "events_url": "https://api.github.com/users/ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/ubiquity/received_events", - "type": "Organization", - "site_admin": false, - "name": "Ubiquity DAO", - "company": null, - "blog": "https://ubiquitydao.xyz", - "location": null, - "email": "github.home@ubiquitydao.xyz", - "hireable": null, - "bio": "The Metaverse Bank.", - "twitter_username": "UbiquityDAO", - "public_repos": 56, - "public_gists": 0, - "followers": 78, - "following": 0, - "created_at": "2020-12-21T00:21:13Z", - "updated_at": "2024-02-15T01:23:04Z" -} diff --git a/cypress/fixtures/metamask-chrome.zip b/cypress/fixtures/metamask-chrome.zip deleted file mode 100644 index 11133c7..0000000 Binary files a/cypress/fixtures/metamask-chrome.zip and /dev/null differ diff --git a/cypress/fixtures/put-config.json b/cypress/fixtures/put-config.json deleted file mode 100644 index dba3ee1..0000000 --- a/cypress/fixtures/put-config.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "total_count": 1, - "installations": [ - { - "id": 47252474, - "account": { - "login": "ubiquity", - "id": 159901852, - "node_id": "O_kgDOCYfonA", - "avatar_url": "https://avatars.githubusercontent.com/u/159901852?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/ubiquity", - "html_url": "https://github.com/ubiquity", - "followers_url": "https://api.github.com/users/ubiquity/followers", - "following_url": "https://api.github.com/users/ubiquity/following{/other_user}", - "gists_url": "https://api.github.com/users/ubiquity/gists{/gist_id}", - "starred_url": "https://api.github.com/users/ubiquity/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/ubiquity/subscriptions", - "organizations_url": "https://api.github.com/users/ubiquity/orgs", - "repos_url": "https://api.github.com/users/ubiquity/repos", - "events_url": "https://api.github.com/users/ubiquity/events{/privacy}", - "received_events_url": "https://api.github.com/users/ubiquity/received_events", - "type": "Organization", - "site_admin": false - }, - "repository_selection": "selected", - "access_tokens_url": "https://api.github.com/app/installations/47252474/access_tokens", - "repositories_url": "https://api.github.com/installation/repositories", - "html_url": "https://github.com/organizations/ubiquity/settings/installations/47252474", - "app_id": 236521, - "app_slug": "ubiquibot", - "target_id": 159901852, - "target_type": "Organization", - "permissions": { - "issues": "write", - "actions": "write", - "members": "read", - "contents": "write", - "metadata": "read", - "pull_requests": "write" - }, - "events": [ - "commit_comment", - "create", - "delete", - "fork", - "gollum", - "issues", - "issue_comment", - "label", - "member", - "membership", - "merge_queue_entry", - "milestone", - "organization", - "public", - "pull_request", - "pull_request_review", - "pull_request_review_comment", - "pull_request_review_thread", - "push", - "release", - "repository", - "repository_dispatch", - "star", - "team", - "team_add", - "watch", - "workflow_dispatch", - "workflow_job", - "workflow_run" - ], - "created_at": "2024-02-13T19:37:30.000+09:00", - "updated_at": "2024-03-11T20:40:33.000+09:00", - "single_file_name": null, - "has_multiple_single_files": false, - "single_file_paths": [], - "suspended_by": null, - "suspended_at": null - } - ] -} diff --git a/cypress/fixtures/put-file.json b/cypress/fixtures/put-file.json deleted file mode 100644 index ef2ffbb..0000000 --- a/cypress/fixtures/put-file.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "content": { - "name": "ubiquibot-config.yml", - "path": ".github/ubiquibot-config.yml", - "sha": "1778de0cb51522aa6a9c16e2c38ba2dfe3bc1a73", - "size": 3598, - "url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/contents/.github/ubiquibot-config.yml?ref=main", - "html_url": "https://github.com/Ubiquity/ubiquibot-config/blob/main/.github/ubiquibot-config.yml", - "git_url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/blobs/1778de0cb51522aa6a9c16e2c38ba2dfe3bc1a73", - "download_url": "https://raw.githubusercontent.com/Ubiquity/ubiquibot-config/main/.github/ubiquibot-config.yml?token=ACK2JIBIHVQCOZNR77YFVNLF47AFK", - "type": "file", - "_links": { - "self": "https://api.github.com/repos/Ubiquity/ubiquibot-config/contents/.github/ubiquibot-config.yml?ref=main", - "git": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/blobs/1778de0cb51522aa6a9c16e2c38ba2dfe3bc1a73", - "html": "https://github.com/Ubiquity/ubiquibot-config/blob/main/.github/ubiquibot-config.yml" - } - }, - "commit": { - "sha": "63937105dea81cda4f7eb8ef4c16b9f12d63a8ff", - "node_id": "C_kwDOLcQmP9oAKDYzOTM3MTA1ZGVhODFjZGE0ZjdlYjhlZjRjMTZiOWYxMmQ2M2E4ZmY", - "url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/commits/63937105dea81cda4f7eb8ef4c16b9f12d63a8ff", - "html_url": "https://github.com/Ubiquity/ubiquibot-config/commit/63937105dea81cda4f7eb8ef4c16b9f12d63a8ff", - "author": { - "name": "Ubiquity", - "email": "ubiquity@users.noreply.github.com", - "date": "2024-03-06T01:00:09Z" - }, - "committer": { - "name": "Ubiquity", - "email": "ubiquity@users.noreply.github.com", - "date": "2024-03-06T01:00:09Z" - }, - "tree": { - "sha": "70bf542678602ebe2fb59c8d255665c0b443fe59", - "url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/trees/70bf542678602ebe2fb59c8d255665c0b443fe59" - }, - "message": "31c1b6ab-f534-43aa-9c02-c04955dadb4d", - "parents": [ - { - "sha": "9313261f872b9af3b904fde7b63af158533e4998", - "url": "https://api.github.com/repos/Ubiquity/ubiquibot-config/git/commits/9313261f872b9af3b904fde7b63af158533e4998", - "html_url": "https://github.com/Ubiquity/ubiquibot-config/commit/9313261f872b9af3b904fde7b63af158533e4998" - } - ], - "verification": { - "verified": false, - "reason": "unsigned", - "signature": null, - "payload": null - } - } -} diff --git a/cypress/fixtures/user-token.json b/cypress/fixtures/user-token.json deleted file mode 100644 index f55ae07..0000000 --- a/cypress/fixtures/user-token.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "provider_token": "123", - "access_token": "123", - "refresh_token": "123", - "token_type": "bearer", - "user": { - "id": "1", - "aud": "authenticated", - "role": "authenticated", - "email": "octocat@github.com", - "email_confirmed_at": "2024-02-12T05:14:35.030647Z", - "phone": "", - "confirmed_at": "2024-02-12T05:14:35.030647Z", - "last_sign_in_at": "2024-02-26T05:05:04.909151Z", - "app_metadata": { - "provider": "github", - "providers": ["github"] - }, - "user_metadata": { - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "email": "octocat@github.com", - "email_verified": true, - "full_name": "Octocat", - "iss": "https://api.github.com", - "name": "Octocat", - "phone_verified": false, - "preferred_username": "Octocat", - "provider_id": "1", - "sub": "1", - "user_name": "Octocat" - }, - "identities": [ - { - "identity_id": "1", - "id": "1", - "user_id": "1", - "identity_data": { - "avatar_url": "https://github.com/images/error/octocat_happy.gif", - "email": "octocat@github.com", - "email_verified": true, - "full_name": "Octocat", - "iss": "https://api.github.com", - "name": "Octocat", - "phone_verified": false, - "preferred_username": "Octocat", - "provider_id": "1", - "sub": "1", - "user_name": "Octocat" - }, - "provider": "github", - "last_sign_in_at": "2024-02-12T05:14:35.023638Z", - "created_at": "2024-02-12T05:14:35.023688Z", - "updated_at": "2024-02-26T05:05:04.904114Z", - "email": "octocat@github.com" - } - ], - "created_at": "2024-02-12T05:14:35.020841Z", - "updated_at": "2024-02-26T05:05:04.917327Z" - } -} diff --git a/cypress/scripts/anvil.sh b/cypress/scripts/anvil.sh new file mode 100644 index 0000000..73f0eeb --- /dev/null +++ b/cypress/scripts/anvil.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# start anvil +anvil --block-time 5 --chain-id 31337 --rpc-url https://rpc.ankr.com/eth \ No newline at end of file diff --git a/jest.config.json b/jest.config.json new file mode 100644 index 0000000..21e90b8 --- /dev/null +++ b/jest.config.json @@ -0,0 +1,10 @@ +{ + "preset": "ts-jest", + "testEnvironment": "node", + "roots": ["./tests"], + "coveragePathIgnorePatterns": ["node_modules", "mocks"], + "collectCoverage": true, + "coverageReporters": ["json", "lcov", "text", "clover", "json-summary"], + "reporters": ["default", "jest-junit", "jest-md-dashboard"], + "coverageDirectory": "coverage" +} diff --git a/knip.ts b/knip.ts deleted file mode 100644 index 596073b..0000000 --- a/knip.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { KnipConfig } from "knip"; - -const config: KnipConfig = { - entry: ["static/scripts/onboarding/onboarding.ts"], - project: ["static/**/*.ts"], - ignoreExportsUsedInFile: true, - ignoreDependencies: ["eslint-config-prettier", "eslint-plugin-prettier", "@octokit/core"], -}; - -export default config; diff --git a/package.json b/package.json index 3269ed7..5fe6084 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,12 @@ "format:lint": "eslint --fix .", "format:prettier": "prettier --write .", "format:cspell": "cspell **/*", - "knip": "knip", - "knip-ci": "knip --no-exit-code --reporter json", + "knip": "knip --config .github/knip.ts", + "knip-ci": "knip --no-exit-code --reporter json --config .github/knip.ts", "prepare": "husky install", - "postinstall": "git submodule update --init --recursive", + "test": "jest --setupFiles dotenv/config --coverage", "cy:open": "cypress open", - "cy:run": "cypress run --browser chrome" + "cy:run": "cypress run" }, "keywords": [ "typescript", @@ -30,42 +30,46 @@ "open-source" ], "dependencies": { - "@ethersproject/providers": "^5.7.2", - "@octokit/core": "^5.1.0", - "@octokit/plugin-create-or-update-text-file": "^4.0.1", - "@octokit/rest": "^20.0.2", - "@supabase/supabase-js": "2.39.7", - "@types/libsodium-wrappers": "^0.7.13", - "@ubiquibot/configuration": "1.1.0", - "@uniswap/permit2-sdk": "^1.2.0", + "@coinbase/wallet-sdk": "^4.2.3", + "@reown/appkit": "^1.5.0", + "@reown/appkit-adapter-ethers5": "^1.5.0", "dotenv": "^16.4.4", - "ethers": "^5.7.2", - "libsodium-wrappers": "^0.7.13", - "yaml": "^2.3.4" + "ethers": "5" }, "devDependencies": { + "@actions/core": "^1.11.1", "@commitlint/cli": "^18.6.1", "@commitlint/config-conventional": "^18.6.2", "@cspell/dict-node": "^4.0.3", "@cspell/dict-software-terms": "^3.3.18", "@cspell/dict-typescript": "^3.1.2", - "@types/adm-zip": "0.5.5", + "@jest/globals": "29.7.0", + "@mswjs/data": "0.16.1", + "@octokit/rest": "^21.0.2", + "@types/jest": "29.5.12", "@types/node": "^20.11.19", + "@types/react": "^18", "@typescript-eslint/eslint-plugin": "^7.0.1", "@typescript-eslint/parser": "^7.0.1", - "adm-zip": "0.5.10", "cspell": "^8.4.0", "cypress": "13.6.6", "esbuild": "^0.20.1", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", + "eslint-plugin-filename-rules": "^1.3.1", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-sonarjs": "^0.24.0", "husky": "^9.0.11", + "jest": "29.7.0", + "jest-junit": "16.0.0", + "jest-md-dashboard": "0.8.0", "knip": "^5.0.1", "lint-staged": "^15.2.2", "npm-run-all": "^4.1.5", "prettier": "^3.2.5", + "react": "^18.3.1", + "simple-git": "^3.27.0", + "ts-jest": "29.1.2", "tsx": "^4.7.1", "typescript": "^5.3.3" }, diff --git a/static/abis.ts b/static/abis.ts new file mode 100644 index 0000000..5efb0a8 --- /dev/null +++ b/static/abis.ts @@ -0,0 +1,173 @@ +export const erc20Abi = [ + { inputs: [{ internalType: "address", name: "_manager", type: "address" }], stateMutability: "nonpayable", type: "constructor" }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "owner", type: "address" }, + { indexed: true, internalType: "address", name: "spender", type: "address" }, + { indexed: false, internalType: "uint256", name: "value", type: "uint256" }, + ], + name: "Approval", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "_burned", type: "address" }, + { indexed: false, internalType: "uint256", name: "_amount", type: "uint256" }, + ], + name: "Burning", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "_to", type: "address" }, + { indexed: true, internalType: "address", name: "_minter", type: "address" }, + { indexed: false, internalType: "uint256", name: "_amount", type: "uint256" }, + ], + name: "Minting", + type: "event", + }, + { anonymous: false, inputs: [{ indexed: false, internalType: "address", name: "account", type: "address" }], name: "Paused", type: "event" }, + { + anonymous: false, + inputs: [ + { indexed: true, internalType: "address", name: "from", type: "address" }, + { indexed: true, internalType: "address", name: "to", type: "address" }, + { indexed: false, internalType: "uint256", name: "value", type: "uint256" }, + ], + name: "Transfer", + type: "event", + }, + { anonymous: false, inputs: [{ indexed: false, internalType: "address", name: "account", type: "address" }], name: "Unpaused", type: "event" }, + { inputs: [], name: "DOMAIN_SEPARATOR", outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], stateMutability: "view", type: "function" }, + { inputs: [], name: "PERMIT_TYPEHASH", outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], stateMutability: "view", type: "function" }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "spender", type: "address" }, + ], + name: "allowance", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "approve", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { inputs: [{ internalType: "uint256", name: "amount", type: "uint256" }], name: "burn", outputs: [], stateMutability: "nonpayable", type: "function" }, + { + inputs: [ + { internalType: "address", name: "account", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "burnFrom", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { inputs: [], name: "decimals", outputs: [{ internalType: "uint8", name: "", type: "uint8" }], stateMutability: "view", type: "function" }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "subtractedValue", type: "uint256" }, + ], + name: "decreaseAllowance", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "addedValue", type: "uint256" }, + ], + name: "increaseAllowance", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "manager", + outputs: [{ internalType: "contract UbiquityAlgorithmicDollarManager", name: "", type: "address" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "to", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "mint", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { inputs: [], name: "name", outputs: [{ internalType: "string", name: "", type: "string" }], stateMutability: "view", type: "function" }, + { + inputs: [{ internalType: "address", name: "", type: "address" }], + name: "nonces", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { inputs: [], name: "pause", outputs: [], stateMutability: "nonpayable", type: "function" }, + { inputs: [], name: "paused", outputs: [{ internalType: "bool", name: "", type: "bool" }], stateMutability: "view", type: "function" }, + { + inputs: [ + { internalType: "address", name: "owner", type: "address" }, + { internalType: "address", name: "spender", type: "address" }, + { internalType: "uint256", name: "value", type: "uint256" }, + { internalType: "uint256", name: "deadline", type: "uint256" }, + { internalType: "uint8", name: "v", type: "uint8" }, + { internalType: "bytes32", name: "r", type: "bytes32" }, + { internalType: "bytes32", name: "s", type: "bytes32" }, + ], + name: "permit", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { inputs: [{ internalType: "string", name: "newName", type: "string" }], name: "setName", outputs: [], stateMutability: "nonpayable", type: "function" }, + { inputs: [{ internalType: "string", name: "newSymbol", type: "string" }], name: "setSymbol", outputs: [], stateMutability: "nonpayable", type: "function" }, + { inputs: [], name: "symbol", outputs: [{ internalType: "string", name: "", type: "string" }], stateMutability: "view", type: "function" }, + { inputs: [], name: "totalSupply", outputs: [{ internalType: "uint256", name: "", type: "uint256" }], stateMutability: "view", type: "function" }, + { + inputs: [ + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transfer", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "address", name: "recipient", type: "address" }, + { internalType: "uint256", name: "amount", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "nonpayable", + type: "function", + }, + { inputs: [], name: "unpause", outputs: [], stateMutability: "nonpayable", type: "function" }, +]; diff --git a/static/display-popup-modal.ts b/static/display-popup-modal.ts new file mode 100644 index 0000000..1e08e8b --- /dev/null +++ b/static/display-popup-modal.ts @@ -0,0 +1,63 @@ +import { appState, explorersUrl } from "./main"; + +export function renderErrorInModal(error: Error) { + const modal = document.getElementById("error-modal"); + const closeButton = document.getElementsByClassName("error-close-modal"); + if (closeButton) { + closeButton[0].addEventListener("click", closeErrorModal); + } + const errorMessageElement = document.getElementById("error-message"); + + if (errorMessageElement) { + errorMessageElement.textContent = `${error.message}\n\n${error.stack || ""}`; + } + + if (modal) { + modal.style.display = "flex"; + } +} + +export function closeErrorModal() { + const modal = document.getElementById("error-modal"); + if (modal) { + modal.style.display = "none"; + } +} + +export function renderSuccessModal(transactionHash: string) { + const modal = document.getElementById("success-modal"); + const closeButton = document.getElementsByClassName("success-close-modal"); + if (closeButton) { + closeButton[0].addEventListener("click", closeSuccessModal); + } + const successMessageElement = document.getElementById("success-message"); + + if (successMessageElement) { + successMessageElement.innerHTML = `You've successfully signed the transaction. Your allowance balance should be updated in a few blocks.

transaction hash: ${transactionHash}`; + const chainId = appState.getChainId(); + const explorerUrl = chainId !== undefined ? explorersUrl[chainId] : ""; + const txLink = document.createElement("a"); + txLink.href = `${explorerUrl}/tx/${transactionHash}`; + txLink.target = "_blank"; + txLink.rel = "noopener noreferrer"; + txLink.style.color = "white"; + txLink.textContent = transactionHash; + + const txHashElement = successMessageElement.querySelector(".tx-hash"); + if (txHashElement) { + txHashElement.innerHTML = ""; + txHashElement.appendChild(txLink); + } + } + + if (modal) { + modal.style.display = "flex"; + } +} + +export function closeSuccessModal() { + const modal = document.getElementById("success-modal"); + if (modal) { + modal.style.display = "none"; + } +} diff --git a/static/handle-approval.ts b/static/handle-approval.ts new file mode 100644 index 0000000..52dd750 --- /dev/null +++ b/static/handle-approval.ts @@ -0,0 +1,166 @@ +import { erc20Abi } from "./abis"; +import { renderErrorInModal, renderSuccessModal } from "./display-popup-modal"; +import { appState, provider, userSigner } from "./main"; +import { getPermit2Address } from "./permit2-addresses"; +import { ethers } from "ethers"; + +const amountInput = document.querySelector(".amount-selector") as HTMLInputElement; +const addressInput = document.querySelector(".token-selector") as HTMLInputElement; +const currentAllowanceAmount = document.querySelector(".current-allowance-amount") as HTMLSpanElement; +const approveButton = document.querySelector(".approve-button") as HTMLButtonElement; +const revokeButton = document.querySelector(".revoke-button") as HTMLButtonElement; + +const red = "1px solid red"; +const green = "1px solid #5af55a"; +const grey = "1px solid rgba(255, 255, 255, 0.1)"; + +function isValidAddress(): boolean { + //reset color + addressInput.style.border = grey; + + const isValid = /^0x[a-fA-F0-9]{40}$/.test(addressInput.value); + + if (!isValid && addressInput.value !== "") { + addressInput.style.border = red; + } + + return isValid; +} + +function isValidAmount(): boolean { + const isValid = !isNaN(Number(amountInput.value)) && Number(amountInput.value) > 0; + + if (isValid) { + amountInput.style.border = green; + } else if (amountInput.value === "") { + amountInput.style.border = grey; + } else { + amountInput.style.border = red; + } + + return isValid; +} + +export async function isApprovalButtonsValid() { + currentAllowanceAmount.textContent = "..."; + const isConnected = appState.getIsConnectedState(); + const isAddressValid = isValidAddress(); + const isAmountValid = isValidAmount(); + + approveButton.disabled = true; + revokeButton.disabled = true; + + if (isAddressValid && isConnected) { + const isSuccess = await getCurrentAllowance(); + if (isSuccess) { + approveButton.disabled = !isAmountValid; + revokeButton.disabled = false; + addressInput.style.border = green; + } else { + addressInput.style.border = red; + } + } +} + +async function getCurrentAllowance(): Promise { + if (!provider) { + console.error("Provider is not initialized"); + return false; + } + + const tokenAddress = addressInput.value; + const permit2Address = getPermit2Address(appState.getChainId() as number); + const userAddress = appState.getAddress(); + const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, provider); + + try { + const symbol = await tokenContract.symbol(); + const decimals = await tokenContract.decimals(); + const allowance = await tokenContract.allowance(userAddress, permit2Address); + const formattedAllowance = ethers.utils.formatUnits(allowance, decimals); + currentAllowanceAmount.textContent = formattedAllowance + " " + symbol; + return true; + } catch (error) { + currentAllowanceAmount.textContent = "not a valid token"; + return false; + } +} + +export function setupApproveButton() { + approveButton.removeEventListener("click", onApproveClick); // ensure no duplicate listeners + approveButton.addEventListener("click", onApproveClick); +} + +async function onApproveClick() { + if (!userSigner) { + console.error("No signer available. Cannot send transaction."); + return; + } + + const originalText = approveButton.textContent; + try { + approveButton.textContent = "Loading..."; + approveButton.disabled = true; + revokeButton.disabled = true; // disable revoke as well to prevent conflicting actions + + const tokenAddress = addressInput.value; + const permit2Address = getPermit2Address(appState.getChainId() as number); + const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, userSigner); + const decimals = await tokenContract.decimals(); + const amount = ethers.utils.parseUnits(amountInput.value, decimals); + + const tx = await tokenContract.approve(permit2Address, amount); + await tx.wait(); + + renderSuccessModal(tx.hash); + + await getCurrentAllowance(); + } catch (error) { + console.error("Error approving allowance:", error); + renderErrorInModal(error as Error); + } finally { + approveButton.textContent = originalText; + await isApprovalButtonsValid(); // re-check the state to restore buttons correctly + } +} + +export function setupRevokeButton() { + revokeButton.removeEventListener("click", onRevokeClick); // ensure no duplicate listeners + revokeButton.addEventListener("click", onRevokeClick); +} + +async function onRevokeClick() { + if (!userSigner) { + console.error("No signer available. Cannot send transaction."); + return; + } + + const originalText = revokeButton.textContent; + try { + revokeButton.textContent = "Loading..."; + revokeButton.disabled = true; + approveButton.disabled = true; // disable approve as well + + const tokenAddress = addressInput.value; + const permit2Address = getPermit2Address(appState.getChainId() as number); + const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, userSigner); + + const tx = await tokenContract.approve(permit2Address, 0); + await tx.wait(); + + renderSuccessModal(tx.hash); + + await getCurrentAllowance(); + } catch (error) { + console.error("Error revoking allowance:", error); + renderErrorInModal(error as Error); + } finally { + revokeButton.textContent = originalText; + await isApprovalButtonsValid(); // re-check state to restore buttons correctly + } +} + +export async function setupButtonValidityListener() { + amountInput.addEventListener("change", isApprovalButtonsValid); + addressInput.addEventListener("change", isApprovalButtonsValid); +} diff --git a/static/index.html b/static/index.html index 3f1caa2..0445a32 100644 --- a/static/index.html +++ b/static/index.html @@ -1,74 +1,82 @@ - + - Onboarding - - - + + + Ubiquity Funding + - -
-

Onboarding

-
-
- 1 - CONFIG -
-
- 2 - APPROVE -
+ + + +
+
+
+

Oops, we have an error

+
-
- - - +
+
-
- - - +
+
+ +
+
+
+

Success

+
-
- -
ubiquibot-config.yml
- +
+
-