From ab23bd7a690c80950680a37de020df542e66d1f1 Mon Sep 17 00:00:00 2001 From: Anthony Burchell Date: Wed, 16 Mar 2022 18:38:20 -0500 Subject: [PATCH] adds deploy scripts to Spoke and documentation --- .gitignore | 1 + README.md | 5 ++ package.json | 2 + scripts/deploy.js | 123 ++++++++++++++++++++++++++++++++++++++++++++++ yarn.lock | 49 ++++++++++++++++++ 5 files changed, 180 insertions(+) create mode 100644 scripts/deploy.js diff --git a/.gitignore b/.gitignore index 4c8a5b057..e0acf4dbd 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ example/imported/ .certs/ certs/ .env +.ret.credentials release/spoke-linux release/spoke-macos release/spoke-win.exe diff --git a/README.md b/README.md index 0beeebeb4..c1f3a7333 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ **[Spoke Documentation](https://hubs.mozilla.com/docs/spoke-creating-projects.html)** +## Deploying custom client for Hubs Cloud + +If you are deploying a custom client, copy your .ret.credentials file from your Hubs folder to this directory. If you do not have one yet, from your Hubs directory run `npm run login` and move the resulting credentials to your Spoke directory. You should then be able to run `npm run deploy` to deploy your custom Spoke. + + ## Features :telescope: **Discover**: Explore images, videos, and 3D models from around the web, all without opening up a new tab. With media integrations from Sketchfab and Google Poly, you'll be on your way to creating a scene in no time. diff --git a/package.json b/package.json index 4c62a15fa..d3f0d7dbf 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "integration-tests": "concurrently --success \"first\" --kill-others \"ava ./test/integration\" \"yarn test-server\"", "update-test-snapshots": "concurrently --success \"first\" --kill-others \"ava -u\" \"yarn test-server\"", "storybook": "start-storybook", + "deploy": "node -r esm -r @babel/register ./scripts/deploy.js", "deploy-storybook": "storybook-to-ghpages" }, "esm": { @@ -102,6 +103,7 @@ "recharts": "^1.8.5", "styled-components": "4.4.0", "styled-icons": "^8.4.2", + "tar": "^6.1.11", "three": "https://github.com/MozillaReality/three.js.git#0f9b0024725f0dd917caa54c2934a4ba1fc12c4f", "three-mesh-bvh": "^0.1.4", "troika-three-text": "^0.44.0", diff --git a/scripts/deploy.js b/scripts/deploy.js new file mode 100644 index 000000000..b0431fe1e --- /dev/null +++ b/scripts/deploy.js @@ -0,0 +1,123 @@ +import { createReadStream, readFileSync, existsSync, unlinkSync } from "fs"; +import { exec } from "child_process"; +import rmdir from "rimraf"; +import tar from "tar"; +import ora from "ora"; +import FormData from "form-data"; +import fetch from "node-fetch"; + +if (!existsSync(".ret.credentials")) { + console.log( + "Not logged in, so cannot deploy. To log in, run npm run login on a Hubs and copy the .ret.credentials file into this folder." + ); + process.exit(0); +} + +const { host, token } = JSON.parse(readFileSync(".ret.credentials")); +console.log(`Deploying to ${host}.`); +const step = ora({ indent: 2 }).start(); + +const getTs = (() => { + const p = n => (n < 10 ? `0${n}` : n); + return () => { + const d = new Date(); + return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}${p( + d.getSeconds() + )}`; + }; +})(); + +(async () => { + const headers = { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json" + }; + + const res = await fetch(`https://${host}/api/ita/configs/spoke`, { headers }); + const spokeConfig = await res.json(); + const buildEnv = {}; + for (const [k, v] of Object.entries(spokeConfig.general)) { + buildEnv[k.toUpperCase()] = v; + } + + const version = getTs(); + + buildEnv.BUILD_VERSION = `1.0.0.${version}`; + buildEnv.ITA_SERVER = ""; + buildEnv.POSTGREST_SERVER = ""; + + const env = Object.assign(process.env, buildEnv); + + for (const d in ["./dist"]) { + rmdir(d, err => { + if (err) { + console.error(err); + process.exit(1); + } + }); + } + + step.text = "Building Spoke."; + + await new Promise((resolve, reject) => { + exec("yarn", {}, err => { + if (err) reject(err); + resolve(); + }); + }); + + await new Promise((resolve, reject) => { + exec("yarn build", { env }, err => { + if (err) reject(err); + resolve(); + }); + }); + + step.text = "Preparing Deploy."; + + step.text = "Packaging Build."; + await tar.c({ gzip: true, C: "dist", file: "_build.tar.gz" }, ["."]); + step.text = `Uploading Build ${buildEnv.BUILD_VERSION}.`; + + let uploadedUrl; + + const runUpload = async attempt => { + if (attempt > 3) { + throw new Error("Upload failed."); + } + + const formData = new FormData(); + formData.append("media", createReadStream("_build.tar.gz")); + formData.append("promotion_mode", "with_token"); + + try { + const res = await fetch(`https://${host}/api/v1/media`, { method: "POST", body: formData }); + const payload = await res.json(); + const url = new URL(payload.origin); + url.searchParams.set("token", payload.meta.access_token); + uploadedUrl = url.toString(); + } catch (e) { + console.log(e); + step.text = `Upload failed. Retrying attempt #${attempt + 1}/3`; + await runUpload(attempt + 1); + } + }; + + await runUpload(0); + unlinkSync("_build.tar.gz"); + + step.text = "Build uploaded, deploying."; + + // Wait for S3 flush, kind of a hack. + await new Promise(res => setTimeout(res, 5000)); + + await fetch(`https://${host}/api/ita/deploy/spoke`, { + headers, + method: "POST", + body: JSON.stringify({ url: uploadedUrl, version }) + }); + + step.text = `Deployed to ${host}.`; + step.succeed(); + process.exit(0); +})(); diff --git a/yarn.lock b/yarn.lock index ae485fd25..2b5635abc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3767,6 +3767,11 @@ chownr@^1.1.1: resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.2.tgz#a18f1e0b269c8a6a5d3c86eb298beb14c3dd7bf6" integrity sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A== +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" @@ -6160,6 +6165,13 @@ fs-minipass@^1.2.5: dependencies: minipass "^2.2.1" +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + fs-readdir-recursive@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" @@ -8936,6 +8948,13 @@ minipass@^2.2.1, minipass@^2.3.5: safe-buffer "^5.1.2" yallist "^3.0.0" +minipass@^3.0.0: + version "3.1.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" + integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== + dependencies: + yallist "^4.0.0" + minizlib@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" @@ -8943,6 +8962,14 @@ minizlib@^1.2.1: dependencies: minipass "^2.2.1" +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + mississippi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" @@ -8987,6 +9014,11 @@ mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" +mkdirp@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mocha@^6.1.4: version "6.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-6.2.0.tgz#f896b642843445d1bb8bca60eabd9206b8916e56" @@ -12968,6 +13000,18 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.3" +tar@^6.1.11: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + telejson@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/telejson/-/telejson-2.2.2.tgz#d61d721d21849a6e4070d547aab302a9bd22c720" @@ -14332,6 +14376,11 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yargs-parser@13.0.0: version "13.0.0" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.0.0.tgz#3fc44f3e76a8bdb1cc3602e860108602e5ccde8b"