From dc41f9a4bac5429ba1543a34e38d9f879a6a1599 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:23:23 -0400 Subject: [PATCH 1/8] working, but no signer --- .github/workflows/jekyll-gh-pages.yml | 55 -------------- .github/workflows/npm-publish.yml | 11 ++- package.json | 4 +- scripts/release.mjs | 101 ++++++++++++++++++++++++++ yarn.lock | 60 ++++++++++++++- 5 files changed, 169 insertions(+), 62 deletions(-) delete mode 100644 .github/workflows/jekyll-gh-pages.yml create mode 100644 scripts/release.mjs diff --git a/.github/workflows/jekyll-gh-pages.yml b/.github/workflows/jekyll-gh-pages.yml deleted file mode 100644 index 2b8b5b0..0000000 --- a/.github/workflows/jekyll-gh-pages.yml +++ /dev/null @@ -1,55 +0,0 @@ -# Sample workflow for building and deploying a Jekyll site to GitHub Pages -name: Deploy Jekyll with GitHub Pages dependencies preinstalled - -on: - # Runs on pushes targeting the default branch - push: - branches: ["main"] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - # Build job - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Build - run: | - yarn - PUBLIC_PATH=/near-bos-webcomponent yarn prod - - name: Setup Pages - uses: actions/configure-pages@v3 - - name: Build with Jekyll - uses: actions/jekyll-build-pages@v1 - with: - source: ./dist - destination: ./_site - - name: Upload artifact - uses: actions/upload-pages-artifact@v2 - - # Deployment job - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v2 diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index ba4da7a..21c29fc 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -20,7 +20,14 @@ jobs: node-version: 20 registry-url: "https://registry.npmjs.org" - run: yarn install - - run: yarn prod - - run: npm publish + - name: Run release script + run: yarn release env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + - name: Commit and push updated package.json + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add package.json + git commit -m "Update package.json with new CID [skip ci]" + git push diff --git a/package.json b/package.json index 10f461d..8d70816 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "nearfs:publish-library:create:car": "ipfs-car pack dist/ --output dist.car", "nearfs:publish-library:upload:car": "NODE_ENV=mainnet node ./node_modules/nearfs/scripts/upload-car.js dist.car", "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", - "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'" + "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'", + "release": "node scripts/release.mjs" }, "eslintConfig": { "extends": [ @@ -97,6 +98,7 @@ "jspm": "^3.1.0", "mini-css-extract-plugin": "^2.2.2", "nearfs": "https://github.com/vgrichina/nearfs", + "ora": "^8.0.1", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "postcss-loader": "^7.0.1", diff --git a/scripts/release.mjs b/scripts/release.mjs new file mode 100644 index 0000000..81076a6 --- /dev/null +++ b/scripts/release.mjs @@ -0,0 +1,101 @@ +import { exec as execCallback } from "child_process"; +import fs from "fs/promises"; +import ora from "ora"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +let currentChildProcess = null; + +async function exec(command) { + return new Promise((resolve, reject) => { + currentChildProcess = execCallback(command, (error, stdout, stderr) => { + currentChildProcess = null; + if (error) { + console.error(`Error executing command: ${command}`); + console.error(stderr); + reject(error); + } else { + resolve(stdout); + } + }); + }); +} + +async function updatePackageJson(cid) { + const packageJsonPath = path.join(__dirname, "package.json"); + let packageJson; + try { + const packageJsonContent = await fs.readFile(packageJsonPath, "utf8"); + packageJson = JSON.parse(packageJsonContent); + } catch (error) { + console.error("Error reading package.json:", error); + throw error; + } + + packageJson.nearfs = packageJson.nearfs || {}; + packageJson.nearfs.cid = cid; + + try { + await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); + } catch (error) { + console.error("Error writing package.json:", error); + throw error; + } +} + +async function runWithSpinner(message, func) { + const spinner = ora(message).start(); + try { + const result = await func(); + spinner.succeed(); + return result; + } catch (error) { + spinner.fail(); + throw error; + } +} + +async function main() { + console.log("Starting release process..."); + + await runWithSpinner("Building production version", () => exec("yarn prod")); + + await runWithSpinner("Creating CAR file", () => + exec("yarn nearfs:publish-library:create:car") + ); + + const uploadOutput = await runWithSpinner("Uploading CAR file", () => + exec("yarn nearfs:publish-library:upload:car") + ); + const cid = uploadOutput.match(/CID: (.+)/)?.[1]; + + if (!cid) { + console.error("Failed to extract CID from upload output"); + process.exit(1); + } + + console.log(`Extracted CID: ${cid}`); + + await runWithSpinner("Updating package.json", () => updatePackageJson(cid)); + + await runWithSpinner("Publishing to npm", () => exec("npm publish")); + + console.log("Release process completed successfully!"); +} + +// Handle interrupts +process.on("SIGINT", () => { + console.log("\nInterrupt received, cleaning up..."); + if (currentChildProcess) { + currentChildProcess.kill("SIGINT"); + } + process.exit(1); +}); + +main().catch((error) => { + console.error("An unexpected error occurred:", error); + process.exit(1); +}); diff --git a/yarn.lock b/yarn.lock index c74d965..15e5e32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6233,7 +6233,7 @@ chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.0.0: +chalk@^5.0.0, chalk@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== @@ -6340,7 +6340,7 @@ cli-cursor@^4.0.0: dependencies: restore-cursor "^4.0.0" -cli-spinners@^2.6.1: +cli-spinners@^2.6.1, cli-spinners@^2.9.2: version "2.9.2" resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== @@ -7190,6 +7190,11 @@ elliptic@^6.5.3, elliptic@^6.5.4, elliptic@^6.5.5: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emoji-regex@^10.3.0: + version "10.3.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" + integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -7854,6 +7859,11 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-east-asian-width@^1.0.0: + version "1.2.0" + resolved "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz#5e6ebd9baee6fb8b7b6bd505221065f0cd91f64e" + integrity sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA== + get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@^1.2.4: version "1.2.4" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" @@ -8849,11 +8859,16 @@ is-typedarray@1.0.0, is-typedarray@^1.0.0: resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== -is-unicode-supported@^1.1.0: +is-unicode-supported@^1.1.0, is-unicode-supported@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== +is-unicode-supported@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz#fdf32df9ae98ff6ab2cedc155a5a6e895701c451" + integrity sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q== + is-weakmap@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" @@ -9308,6 +9323,14 @@ log-symbols@^5.1.0: chalk "^5.0.0" is-unicode-supported "^1.1.0" +log-symbols@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz#bb95e5f05322651cac30c0feb6404f9f2a8a9439" + integrity sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== + dependencies: + chalk "^5.3.0" + is-unicode-supported "^1.3.0" + long@^5.0.0: version "5.2.3" resolved "https://registry.npmjs.org/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" @@ -10590,6 +10613,21 @@ ora@^6.3.0: strip-ansi "^7.0.1" wcwidth "^1.0.1" +ora@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz#6dcb9250a629642cbe0d2df3a6331ad6f7a2af3e" + integrity sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ== + dependencies: + chalk "^5.3.0" + cli-cursor "^4.0.0" + cli-spinners "^2.9.2" + is-interactive "^2.0.0" + is-unicode-supported "^2.0.0" + log-symbols "^6.0.0" + stdin-discarder "^0.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -12449,6 +12487,11 @@ stdin-discarder@^0.1.0: dependencies: bl "^5.0.0" +stdin-discarder@^0.2.1: + version "0.2.2" + resolved "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz#390037f44c4ae1a1ae535c5fe38dc3aba8d997be" + integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== + stop-iteration-iterator@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz#6a60be0b4ee757d1ed5254858ec66b10c49285e4" @@ -12505,6 +12548,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^7.0.0: + version "7.2.0" + resolved "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -12526,7 +12578,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: +strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== From d17cb6c75dc174d164af76c6b450a765dceff011 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:21:07 -0400 Subject: [PATCH 2/8] complete script --- package.json | 5 +- scripts/release.mjs | 147 ++++++++++++++++++++++++++++---------------- 2 files changed, 98 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 8d70816..74cce7e 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,11 @@ "test:ui": "npx playwright test --ui", "test:ui:codespaces": "npx playwright test --ui-host=0.0.0.0", "nearfs:publish-library:create:car": "ipfs-car pack dist/ --output dist.car", - "nearfs:publish-library:upload:car": "NODE_ENV=mainnet node ./node_modules/nearfs/scripts/upload-car.js dist.car", + "nearfs:publish-library:upload:car": "node ./node_modules/nearfs/scripts/upload-car.js dist.car", "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'", - "release": "node scripts/release.mjs" + "release": "node scripts/release.mjs", + "clean": "rimraf dist dist.car" }, "eslintConfig": { "extends": [ diff --git a/scripts/release.mjs b/scripts/release.mjs index 81076a6..e799cb5 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -6,44 +6,66 @@ import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +const rootDir = path.resolve(__dirname, ".."); let currentChildProcess = null; -async function exec(command) { +class ValidationError extends Error { + constructor(message) { + super(message); + this.name = "ValidationError"; + } +} + +function parseArgs() { + const [signerAccount, signerKey, network = "mainnet"] = process.argv.slice(2); + + if (!signerAccount || !signerKey) { + console.warn( + "Missing arguments: signer account and signer key\n" + + "Usage: node release.js [network]\n\n" + + "Will attempt to sign from keychain..." + ); + } + + if (network !== "mainnet" && network !== "testnet") { + throw new ValidationError(`Invalid network: ${network}`); + } + + console.log(`Using network: ${network}\n`); + return { signerAccount, signerKey, network }; +} + +async function exec(command, env = {}) { return new Promise((resolve, reject) => { - currentChildProcess = execCallback(command, (error, stdout, stderr) => { - currentChildProcess = null; - if (error) { - console.error(`Error executing command: ${command}`); - console.error(stderr); - reject(error); - } else { - resolve(stdout); + const options = { env: { ...process.env, ...env } }; + let output = ""; + currentChildProcess = execCallback( + command, + options, + (error, stdout, stderr) => { + currentChildProcess = null; + if (error) { + reject(error); + } else { + resolve(output); + } } + ); + currentChildProcess.stdout.on("data", (data) => { + output += data.toString(); + }); + currentChildProcess.stderr.on("data", (data) => { + output += data.toString(); }); }); } async function updatePackageJson(cid) { - const packageJsonPath = path.join(__dirname, "package.json"); - let packageJson; - try { - const packageJsonContent = await fs.readFile(packageJsonPath, "utf8"); - packageJson = JSON.parse(packageJsonContent); - } catch (error) { - console.error("Error reading package.json:", error); - throw error; - } - - packageJson.nearfs = packageJson.nearfs || {}; - packageJson.nearfs.cid = cid; - - try { - await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); - } catch (error) { - console.error("Error writing package.json:", error); - throw error; - } + const packageJsonPath = path.join(rootDir, "package.json"); + let packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8")); + packageJson.nearfs = { ...packageJson.nearfs, cid }; + await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)); } async function runWithSpinner(message, func) { @@ -59,43 +81,64 @@ async function runWithSpinner(message, func) { } async function main() { - console.log("Starting release process..."); + try { + const { signerAccount, signerKey, network } = parseArgs(); - await runWithSpinner("Building production version", () => exec("yarn prod")); + console.log("Starting release process..."); - await runWithSpinner("Creating CAR file", () => - exec("yarn nearfs:publish-library:create:car") - ); + await runWithSpinner("Building production version", () => + exec("yarn prod") + ); + const output = await runWithSpinner("Creating CAR file", () => + exec("yarn nearfs:publish-library:create:car") + ); - const uploadOutput = await runWithSpinner("Uploading CAR file", () => - exec("yarn nearfs:publish-library:upload:car") - ); - const cid = uploadOutput.match(/CID: (.+)/)?.[1]; + const lines = output.trim().split("\n"); + const cid = lines[lines.length - 1].trim(); - if (!cid) { - console.error("Failed to extract CID from upload output"); - process.exit(1); - } + if (!cid) throw new Error("Failed to extract CID from output"); - console.log(`Extracted CID: ${cid}`); + console.log(`Extracted CID: ${cid}`); - await runWithSpinner("Updating package.json", () => updatePackageJson(cid)); + await runWithSpinner("Uploading CAR file", () => + exec("yarn nearfs:publish-library:upload:car", { + NODE_ENV: network, + NEAR_SIGNER_KEY: signerKey, + NEAR_SIGNER_ACCOUNT: signerAccount, + }) + ); - await runWithSpinner("Publishing to npm", () => exec("npm publish")); + await runWithSpinner("Updating package.json", () => updatePackageJson(cid)); - console.log("Release process completed successfully!"); + console.log("Release process completed successfully!"); + } catch (error) { + if (error instanceof ValidationError) { + console.error(error.message); + } else { + console.error("An unexpected error occurred:", error); + } + process.exit(1); + } } -// Handle interrupts -process.on("SIGINT", () => { - console.log("\nInterrupt received, cleaning up..."); +async function cleanup() { if (currentChildProcess) { currentChildProcess.kill("SIGINT"); } - process.exit(1); -}); + try { + await exec("yarn clean"); + console.log("\nCleanup completed."); + } catch (error) { + console.error("\nError during cleanup:", error); + } +} + +process.on("SIGINT", async () => { + console.log("\nInterrupt received. Starting cleanup process...\n"); + + await cleanup(); -main().catch((error) => { - console.error("An unexpected error occurred:", error); process.exit(1); }); + +main(); From d8620b0afff5f01e4064f3a5b3dc6fb2df6ec9d9 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:33:49 -0400 Subject: [PATCH 3/8] release cleanup --- .github/workflows/npm-publish.yml | 9 ++++++--- scripts/release.mjs | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 21c29fc..9f97140 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -22,12 +22,15 @@ jobs: - run: yarn install - name: Run release script run: yarn release - env: - NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} - - name: Commit and push updated package.json + - name: Commit and push updated package.json (nearfs cid) run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add package.json git commit -m "Update package.json with new CID [skip ci]" git push + - name: Publish to NPM + run: npm publish + env: + NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} + diff --git a/scripts/release.mjs b/scripts/release.mjs index e799cb5..62f304f 100644 --- a/scripts/release.mjs +++ b/scripts/release.mjs @@ -98,7 +98,7 @@ async function main() { if (!cid) throw new Error("Failed to extract CID from output"); - console.log(`Extracted CID: ${cid}`); + console.log(`CID: ${cid}`); await runWithSpinner("Uploading CAR file", () => exec("yarn nearfs:publish-library:upload:car", { @@ -110,7 +110,7 @@ async function main() { await runWithSpinner("Updating package.json", () => updatePackageJson(cid)); - console.log("Release process completed successfully!"); + console.log("Release successfully prepared!"); } catch (error) { if (error instanceof ValidationError) { console.error(error.message); From c5e8f2edca8447cca8fd3234452cd0faa6acc06a Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:40:20 -0400 Subject: [PATCH 4/8] rename prepare-release --- .github/workflows/npm-publish.yml | 11 ++++------- package.json | 2 +- scripts/{release.mjs => prepare-release.mjs} | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) rename scripts/{release.mjs => prepare-release.mjs} (97%) diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 9f97140..0e8d239 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,7 +1,4 @@ -# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created -# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages - -name: Node.js Package +name: Publish package on: release: @@ -20,14 +17,14 @@ jobs: node-version: 20 registry-url: "https://registry.npmjs.org" - run: yarn install - - name: Run release script - run: yarn release + - name: Prepare the release + run: yarn prepare:release - name: Commit and push updated package.json (nearfs cid) run: | git config --local user.email "action@github.com" git config --local user.name "GitHub Action" git add package.json - git commit -m "Update package.json with new CID [skip ci]" + git commit -m "Update package.json with new nearfs cid [skip ci]" git push - name: Publish to NPM run: npm publish diff --git a/package.json b/package.json index 74cce7e..4ee6a4d 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "nearfs:publish-library:upload:car": "node ./node_modules/nearfs/scripts/upload-car.js dist.car", "fmt": "prettier --write '**/*.{js,jsx,ts,tsx,json}'", "fmt:check": "prettier --check '**/*.{js,jsx,ts,tsx,json}'", - "release": "node scripts/release.mjs", + "prepare:release": "node scripts/prepare-release.mjs", "clean": "rimraf dist dist.car" }, "eslintConfig": { diff --git a/scripts/release.mjs b/scripts/prepare-release.mjs similarity index 97% rename from scripts/release.mjs rename to scripts/prepare-release.mjs index 62f304f..8786f66 100644 --- a/scripts/release.mjs +++ b/scripts/prepare-release.mjs @@ -84,7 +84,7 @@ async function main() { try { const { signerAccount, signerKey, network } = parseArgs(); - console.log("Starting release process..."); + console.log("Preparing a release..."); await runWithSpinner("Building production version", () => exec("yarn prod") @@ -110,7 +110,7 @@ async function main() { await runWithSpinner("Updating package.json", () => updatePackageJson(cid)); - console.log("Release successfully prepared!"); + console.log("Release prepared successfully!"); } catch (error) { if (error instanceof ValidationError) { console.error(error.message); From 15d58e38f74bc5c006a8ee11ea132a8469eef856 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:06:40 -0400 Subject: [PATCH 5/8] adds asset-manifest.json --- webpack.config.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/webpack.config.js b/webpack.config.js index 22de46e..d4f2523 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,6 +7,7 @@ const CopyWebpackPlugin = require("copy-webpack-plugin"); const { merge } = require("webpack-merge"); const loadPreset = require("./config/presets/loadPreset"); const loadConfig = (mode) => require(`./config/webpack.${mode}.js`)(mode); +const { WebpackManifestPlugin } = require("webpack-manifest-plugin"); module.exports = function (env) { const { mode = "production" } = env || {}; @@ -93,6 +94,19 @@ module.exports = function (env) { process: "process/browser", Buffer: [require.resolve("buffer/"), "Buffer"], }), + new WebpackManifestPlugin({ + fileName: "asset-manifest.json", + publicPath: "/", + generate: (seed, files, entrypoints) => { + const entrypointFiles = entrypoints.main.filter( + (fileName) => !fileName.endsWith(".map") + ); + + return { + entrypoints: entrypointFiles, + }; + }, + }), ], }, loadConfig(mode), From 896e3293b6bf2cd49cc21ebe1b306979bf3b3f27 Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:33:28 -0400 Subject: [PATCH 6/8] update readme --- README.md | 95 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index f0eb70a..96b3baa 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,46 @@ -# NEAR BOS Web Component ( custom element ) + + + -This is a Proof of Concept of embedding a NEAR BOS widget into any web application as a Web Component / Custom element. +
-Just load react production react bundles into your index.html as shown below, and use the `near-social-viewer` custom element to embed the BOS widget. +

near bos web component

-```html - - - - - - Near social - - - - -

NEAR BOS embeddable custom element

- - - -``` - -## Setup & Development +

+ Easily embed a near social widget into any web app and deploy to web4. +

-Initialize repo: +
-```cmd -yarn -``` +`near-social-viewer` is a [web component (custom element)](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) that implements the [near-social-vm](https://github.com/NearSocial/VM) for rendering code stored on-chain in the [SocialDB](https://github.com/NearSocial/social-db) smart contract (social.near). It is the simplest way to create your own [near social viewer](https://github.com/NearSocial/viewer) and it is the easiest method for embedding [Widgets](https://thewiki.near.page/near.social_widgets) into any web application. -Start development version: +## Usage -```cmd -yarn start -``` +
+ Via CDN -Production build: +Include the following script tags in your HTML: -```cmd -yarn prod +```html + + ``` -Serve the production build: +Be sure to replace "REPLACE_WITH_BUNDLE_HASH" with the respective hash, which can be found via the asset-manifest: -```cmd -yarn serve prod + + +
+ +Once included, you can use the web component in your HTML: + +```html + ``` ## Attributes -The `near-social-viewer` web component supports several attributes: +The web component supports several attributes: * `src`: the src of the widget to render (e.g. `devs.near/widget/default`) * `code`: raw, valid, stringified widget code to render (e.g. `"return

hello world

"`) @@ -80,6 +65,32 @@ To support specific features of the VM or an accompanying development server, pr } ``` +## Setup & Local Development + +Initialize repo: + +```cmd +yarn +``` + +Start development version: + +```cmd +yarn start +``` + +Production build: + +```cmd +yarn prod +``` + +Serve the production build: + +```cmd +yarn serve prod +``` + ## Adding VM Custom Elements Since [NearSocial/VM v2.1.0](https://github.com/NearSocial/VM/blob/master/CHANGELOG.md#210), a gateway can register custom elements where the key is the name of the element, and the value is a function that returns a React component. For example: From ab69a3d9265f49cfc2f7b6318c5c60cdab34a18f Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:51:47 -0400 Subject: [PATCH 7/8] reorders sections --- README.md | 89 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 96b3baa..bcccd52 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ +
@@ -65,6 +66,38 @@ To support specific features of the VM or an accompanying development server, pr } ``` +## Local Widget Development + +There are several strategies for accessing local widget code during development. + +### Proxy RPC + +The recommended, least invasive strategy is to provide a custom RPC url that proxies requests for widget code. Widget code is stored in the [socialdb](https://github.com/NearSocial/social-db), and so it involves an RPC request to get the stringified code. We can proxy this request to use local data instead. + +Either build a custom proxy server, or use [bos-workspace](https://github.com/nearbuilders/bos-workspace) which provides a proxy by default and will automatically inject it to the `rpc` attribute if you provide the path to your web component's dist, or a link to it stored on [NEARFS](https://github.com/vgrichina/nearfs). See more in [Customizing the Gateway](https://github.com/NEARBuilders/bos-workspace?tab=readme-ov-file#customizing-the-gateway). + +### Redirect Map + +The NEAR Social VM supports a feature called `redirectMap` which allows you to load widgets from other sources than the on-chain social db. An example redirect map can look like this: + +```json +{ "devhub.near/widget/devhub.page.feed": { "code": "return 'hello';" } } +``` + +The result of applying this redirect map is that the widget `devhub.near/widget/devhub.page.feed` will be replaced by a string that says `hello`. + +The `near-social-viewer` web component supports loading a redirect map from the session storage, which is useful when using the viewer for local development or test pipelines. + +By setting the session storage key `nearSocialVMredirectMap` to the JSON value of the redirect map, the web component will pass this to the VM Widget config. + +Another option is to use the same mechanism as [near-discovery](https://github.com/near/near-discovery/), where you can load components from a locally hosted [bos-loader](https://github.com/near/bos-loader) by adding the key `flags` to localStorage with the value `{"bosLoaderUrl": "http://127.0.0.1:3030" }`. There also exists an input at [dev.near.org/flags](https://dev.near.org/flags) to input this url. + +### Hot Reload + +The above strategies require changes to be reflected either on page reload, or from a fresh rpc request. For faster updates, there is an option in `config` to enable hot reload via dev.hotreload (see [configurations](#configuration-options)). This will try to connect to a web socket server on the same port, or via the provided url, to use redirectMap with most recent data. + +This feature works best when accompanied with [bos-workspace](https://github.com/nearbuilders/bos-workspace), which will automatically inject a config to the attribute if you provide the path to your web component's dist, or a link to it stored on [NEARFS](https://github.com/vgrichina/nearfs). See more in [Customizing the Gateway](https://github.com/NEARBuilders/bos-workspace?tab=readme-ov-file#customizing-the-gateway). It can be disabled with the `--no-hot` flag. + ## Setup & Local Development Initialize repo: @@ -73,7 +106,7 @@ Initialize repo: yarn ``` -Start development version: +Start the development version: ```cmd yarn start @@ -88,7 +121,7 @@ yarn prod Serve the production build: ```cmd -yarn serve prod +yarn serve:prod ``` ## Adding VM Custom Elements @@ -128,6 +161,16 @@ bos-workspace dev -g ./path/to/dist This will start a local dev server using the custom gateway, so you may develop your local widgets through it with access to the custom element. +## Configuring Ethers + +Since [NearSocial/VM v1.3.0](https://github.com/NearSocial/VM/blob/master/CHANGELOG.md#130), the VM has exposed Ethers and ethers in the global scope, as well as a Web3Connect custom element for bringing up wallet connect. + +There already exists support for most common EVM chains, but to add a new chain to your web3 provider, find your chain on [ChainList](https://chainlist.org/) and then add the necessary details to the [chains.json](./src/utils/web4/chains.json). Be sure to include a testnet configuration as well. This will enable you to connect to the specified chain when using `` within a widget running inside your custom web component. + +You can configure the projectId and appMetadata in [utils/web4/ethers.js](./src/utils/web3/ethers.js) as well. + +For more information on how to utilize [Ethers.js](https://docs.ethers.org/v6/) in your widgets, see [NEAR for Ethereum developers](https://docs.near.org/tutorials/near-components/ethers-js). To see a list of existing EVM components built by the community, see [here](https://near.social/hackerhouse.near/widget/EVMComponents). + ## Running Playwright tests To be able to run the [playwright](https://playwright.dev) tests, you first need to install the dependencies. You can see how this is done in [.devcontainer/post-create.sh](./.devcontainer/post-create.sh) which is automatically executed when opening this repository in a github codespace. @@ -154,48 +197,6 @@ yarn test:ui:codespaces In general it is a good practice, and very helpful for reviewers and users of this project, that all use cases are covered in Playwright tests. Also, when contributing, try to make your tests as simple and clear as possible, so that they serve as examples on how to use the functionality. -## Local Widget Development - -There are several strategies for accessing local widget code during development. - -### Proxy RPC - -The recommended, least invasive strategy is to provide a custom RPC url that proxies requests for widget code. Widget code is stored in the [socialdb](https://github.com/NearSocial/social-db), and so it involves an RPC request to get the stringified code. We can proxy this request to use our local code instead. - -You can build a custom proxy server, or [bos-workspace](https://github.com/nearbuilders/bos-workspace) provides a proxy by default and will automatically inject it to the `rpc` attribute if you provide the path to your web component's dist, or a link to it stored on [NEARFS](https://github.com/vgrichina/nearfs). See more in [Customizing the Gateway](https://github.com/NEARBuilders/bos-workspace?tab=readme-ov-file#customizing-the-gateway). - -### Redirect Map - -The NEAR social VM supports a feature called `redirectMap` which allows you to load widgets from other sources than the on chain social db. An example redirect map can look like this: - -```json -{ "devhub.near/widget/devhub.page.feed": { "code": "return 'hello';" } } -``` - -The result of applying this redirect map is that the widget `devhub.near/widget/devhub.page.feed` will be replaced by a string that says `hello`. - -The `near-social-viewer` web component supports loading a redirect map from the session storage, which is useful when using the viewer for local development or test pipelines. - -By setting the session storage key `nearSocialVMredirectMap` to the JSON value of the redirect map, the web component will pass this to the VM Widget config. - -You can also use the same mechanism as [near-discovery](https://github.com/near/near-discovery/) where you can load components from a locally hosted [bos-loader](https://github.com/near/bos-loader) by adding the key `flags` to localStorage with the value `{"bosLoaderUrl": "http://127.0.0.1:3030" }`. - -### Hot Reload - -The above strategies require changes to be reflected either on page reload, or from a fresh rpc request. For faster updates, there is an option in `config` to enable hot reload via dev.hotreload (see [configurations](#configuration-options)), which will try to connect to a web socket server on the same port and use redirectMap with most recent data. - -This feature works best when accompanied with [bos-workspace](https://github.com/nearbuilders/bos-workspace), which will automatically inject a config to the attribute if you provide the path to your web component's dist, or a link to it stored on [NEARFS](https://github.com/vgrichina/nearfs). See more in [Customizing the Gateway](https://github.com/NEARBuilders/bos-workspace?tab=readme-ov-file#customizing-the-gateway). It can be disabled with the `--no-hot` flag. - -## Configuring Ethers - -Since [NearSocial/VM v1.3.0](https://github.com/NearSocial/VM/blob/master/CHANGELOG.md#130), the VM has exposed Ethers and ethers in the global scope, as well as a Web3Connect custom element for bringing up wallet connect. - -There already exists support for most common EVM chains, but to add a new chain to your web3 provider, find your chain on [ChainList](https://chainlist.org/) and then add the necessary details to the [chains.json](./src/utils/web4/chains.json). Be sure to include a testnet configuration as well. This will enable you to connect to the specified chain when using `` within a widget running inside your custom web component. - -You can configure the projectId and appMetadata in [utils/web4/ethers.js](./src/utils/web3/ethers.js) as well. - -For more information on how to utilize [Ethers.js](https://docs.ethers.org/v6/) in your widgets, see [NEAR for Ethereum developers](https://docs.near.org/tutorials/near-components/ethers-js). To see a list of existing EVM components built by the community, see [here](https://near.social/hackerhouse.near/widget/EVMComponents). - ## Landing page for SEO friendly URLs Normally, the URL path decides which component to be loaded. The path `/devhub.near/widget/app` will load the `app` component from the `devhub.near` account. DevHub is an example of a collection of many components that are part of a big app, and the `app` component is just a proxy to components that represent a `page`. Which page to display is controlled by the `page` query string parameter, which translates to `props.page` in the component. From c0551e97025bcd607ef5cfc2170d5145c5e9546d Mon Sep 17 00:00:00 2001 From: Elliot Braem <16282460+elliotBraem@users.noreply.github.com> Date: Thu, 11 Jul 2024 18:02:15 -0400 Subject: [PATCH 8/8] moves test docs to separate readme --- README.md | 26 ------------ playwright-tests/README.md | 82 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 26 deletions(-) create mode 100644 playwright-tests/README.md diff --git a/README.md b/README.md index bcccd52..cfc90fc 100644 --- a/README.md +++ b/README.md @@ -171,32 +171,6 @@ You can configure the projectId and appMetadata in [utils/web4/ethers.js](./src/ For more information on how to utilize [Ethers.js](https://docs.ethers.org/v6/) in your widgets, see [NEAR for Ethereum developers](https://docs.near.org/tutorials/near-components/ethers-js). To see a list of existing EVM components built by the community, see [here](https://near.social/hackerhouse.near/widget/EVMComponents). -## Running Playwright tests - -To be able to run the [playwright](https://playwright.dev) tests, you first need to install the dependencies. You can see how this is done in [.devcontainer/post-create.sh](./.devcontainer/post-create.sh) which is automatically executed when opening this repository in a github codespace. - -When the dependencies are set up, you can run the test suite in your terminal: - -```bash -yarn test -``` - -To run tests visually in the playwright UI, you can use the following command: - -```bash -yarn test:ui -``` - -This will open the playwright UI in a browser, where you can run single tests, and also inspect visually. - -If you want to use the playwright UI from a github codespace, you can use this command: - -```bash -yarn test:ui:codespaces -``` - -In general it is a good practice, and very helpful for reviewers and users of this project, that all use cases are covered in Playwright tests. Also, when contributing, try to make your tests as simple and clear as possible, so that they serve as examples on how to use the functionality. - ## Landing page for SEO friendly URLs Normally, the URL path decides which component to be loaded. The path `/devhub.near/widget/app` will load the `app` component from the `devhub.near` account. DevHub is an example of a collection of many components that are part of a big app, and the `app` component is just a proxy to components that represent a `page`. Which page to display is controlled by the `page` query string parameter, which translates to `props.page` in the component. diff --git a/playwright-tests/README.md b/playwright-tests/README.md new file mode 100644 index 0000000..c2998f0 --- /dev/null +++ b/playwright-tests/README.md @@ -0,0 +1,82 @@ +# Testing Guide + +This project uses [playwright](https://playwright.dev/) for end-to-end testing. Please become familiar with this documentation. + +In general it is a good practice, and very helpful for reviewers and users of this project, that all use cases are covered in Playwright tests. Also, when contributing, try to make your tests as simple and clear as possible, so that they serve as examples on how to use the functionality. + +## Writing tests + +Tests should be written for each change or addition to the codebase. +If a new feature is introduced, tests should be written to validate its functionality. If a bug is fixed, tests should be written to prevent regression. Writing tests not only safeguards against future breaks by other developers but also accelerates development by minimizing manual coding and browser interactions. + +When writing tests, remember to: + +- Test user-visible behavior +- Make tests as isolated as possible +- Avoid testing third-party dependencies + +> **[LEARN BEST PRACTICES](https://playwright.dev/docs/best-practices)** + +See the [cookbook](#cookbook) for help in covering scenerios. It is possible to [generate tests](https://playwright.dev/docs/codegen) via the [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright). + +## Running tests + +## Running Playwright tests + +To be able to run the [playwright](https://playwright.dev) tests, you first need to install the dependencies. You can see how this is done in [../.devcontainer/post-create.sh](./.devcontainer/post-create.sh) which is automatically executed when opening this repository in a github codespace. + +When the dependencies are set up, you can run the test suite in your terminal: + +```bash +yarn test +``` + + + +To run tests visually in the playwright UI, you can use the following command: + +```bash +yarn test:ui +``` + +This will open the playwright UI in a browser, where you can run single tests, and also inspect visually. + +If you want to use the playwright UI from a github codespace, you can use this command: + +```bash +yarn test:ui:codespaces +``` + +Or to run tests through VS Code, see [Getting started - VS Code](https://playwright.dev/docs/getting-started-vscode). + +## Recording video + +You may automatically record video with your tests by setting + +```json +"use": { + "video": "on" +} +``` + +in the [playwright.config.js](../playwright.config.js). After running tests, you will find the output as a `.webm` in `./test-results`. Then, [convert to MP4](https://video.online-convert.com/convert/webm-to-mp4) and share. + +It is encouraged to include video in pull requests in order to demonstrate functionality and prove thorough testing. + +## Cookbook + +### Capturing the VM Confirmation Popup + +Currently, none of the tests post actual transactions to the smart contracts. Still you should try writing your tests so that they do the actual function call, but just skip the final step of sending the transaction. You can do this by capturing the transaction confirmation popup provided by the NEAR Social VM. + +```javascript +const expectedTransactionData = {}; + +// click button that triggers transaction +await page.getByRole("button", { name: "Commit" }).nth(1).click(); + +const transactionObj = JSON.parse(await page.locator("div.modal-body code").innerText()); + +// do something with transactionObj +expect(transactionObj).toMatchObject(expectedTransactionData); +```