diff --git a/.github/workflows/sfab-gh-pages.yml b/.github/workflows/sfab-gh-pages.yml new file mode 100644 index 000000000..f53a04aad --- /dev/null +++ b/.github/workflows/sfab-gh-pages.yml @@ -0,0 +1,51 @@ +# Sample workflow for building and deploying a sfab site to GitHub Pages +name: Deploy sfab 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: Setup Pages + uses: actions/configure-pages@v3 + - name: Use Node.js and Build with sfab + uses: actions/setup-node@v3 + with: + node-version: latest + - run: npm run build + - 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/.gitignore b/.gitignore index de7149dd6..88904c647 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ node_modules .nyc_output/ npm-debug.log coverage/ +_site \ No newline at end of file diff --git a/.npmignore b/.npmignore index 8623bd2da..ddb953c48 100644 --- a/.npmignore +++ b/.npmignore @@ -5,4 +5,6 @@ bin/e2e-test.sh test docs examples -script \ No newline at end of file +script +www +dist \ No newline at end of file diff --git a/docs/adapters.md b/docs/adapters.md index 77f63df31..350f5abed 100644 --- a/docs/adapters.md +++ b/docs/adapters.md @@ -1,5 +1,7 @@ --- -permalink: /docs/adapters/ +title: Adapters +layout: layouts/docs.html +permalink: /adapters.html --- # Adapters @@ -8,20 +10,22 @@ Adapters are the interface to the service you want your hubot to run on. Hubot includes two official adapters: -* [Shell](./adapters/shell.md), i.e. for use with development -* [Campfire](./adapters/campfire.md) +* [Shell](./adapters/shell.html), i.e. for use with development +* [Campfire](./adapters/campfire.html) There are Third-party adapters available for most chat services. Here are the most popular ones: +* [Discord](https://github.com/hubot-friends/hubot-discord) +* [IRC](https://github.com/hubot-friends/hubot-irc) +* [Slack](https://github.com/hubot-friends/hubot-slack) +* [MS Teams](https://github.com/hubot-friends/hubot-ms-teams) * [Gitter](https://github.com/huafu/hubot-gitter2) * [HipChat](https://github.com/hipchat/hubot-hipchat) -* [IRC](https://github.com/nandub/hubot-irc) * [Rocket.Chat](https://github.com/RocketChat/hubot-rocketchat) -* [Slack](https://github.com/slackhq/hubot-slack) * [XMPP](https://github.com/markstory/hubot-xmpp) Browse all [repositories with the `hubot-adapter` topic on GitHub](https://github.com/search?q=topic%3Ahubot-adapter&type=Repositories) or [search for adapters on NPM](https://www.npmjs.com/search?q=hubot%20adapter&ranking=popularity). Add the `hubot-adapter` [topic](https://help.github.com/articles/classifying-your-repository-with-topics/) to your repository on GitHub to include it in this list. ## Writing Your Own Adapter -Interested in adding your own adapter? Check out our documentation for [developing adapters](./adapters/development.md) +Interested in adding your own adapter? Check out our documentation for [developing adapters](./adapters/development.html) diff --git a/docs/adapters/campfire.md b/docs/adapters/campfire.md index bd519e036..92cd83416 100644 --- a/docs/adapters/campfire.md +++ b/docs/adapters/campfire.md @@ -1,29 +1,24 @@ --- -permalink: /docs/adapters/campfire/ +title: Campfire adapter +layout: layouts/docs.html +permalink: /adapters/campfire.html --- # Campfire adapter -[Campfire](http://campfirenow.com/) is a web based chat application built by -[37signals](http://37signals.com). The Campfire adapter is one of the original -adapters in Hubot. +[Campfire](http://campfirenow.com/) is a web based chat application built by [37signals](http://37signals.com). The Campfire adapter is one of the original adapters in Hubot. ## Getting Started -You will need a Campfire account to start, which you can -[sign up for free](https://signup.37signals.com/campfire/free/signup/new). +You will need a Campfire account to start. -Next, you will need to create a user on your Campfire account for your Hubot, -then give it access so it can join to your rooms. You will need to create a room -if you haven't already. +Next, you will need to create a user on your Campfire account for your Hubot, then give it access so it can join to your rooms. You will need to create a room if you haven't already. -Hubot defaults to using its [shell](./shell.md), so to use Campfire instead, you -can run hubot with `-a campfire`: +Hubot defaults to using its [shell](./shell.html), so to use Campfire instead, you can run hubot with `-a campfire`: % bin/hubot -a campfire -If you are deploying to Heroku or using foreman, you need to make -sure the hubot is called with `-a campfire` in the `Procfile`: +If you are deploying to Heroku or using foreman, you need to make sure the hubot is called with `-a campfire` in the `Procfile`: web: bin/hubot -a campfire -n Hubot diff --git a/docs/adapters/development.md b/docs/adapters/development.md index 35ead4a32..12c427fe9 100644 --- a/docs/adapters/development.md +++ b/docs/adapters/development.md @@ -1,5 +1,7 @@ --- -permalink: /docs/adapters/development/ +title: Development adapter +layout: layouts/docs.html +permalink: /adapters/development.html --- # Development adapter @@ -65,7 +67,7 @@ exports.use = (robot) => new Sample(robot) } ``` -7. Generate your Hubot using the `yo hubot` [command](https://hubot.github.com/docs/) +7. Generate your Hubot using the `npx hubot --create myhubot` 8. Change working directories to the `hubot` you created in step 7. 9. Now perform an `npm link` to add your adapter to `hubot` - `npm link ../hubot-sample` @@ -115,10 +117,10 @@ Another option is to load the file from local disk. "dependencies": { }, "peerDependencies": { - "hubot": ">=4.2.0" + "hubot": ">=9" }, "devDependencies": { - "coffeescript": ">=1.2.0" + "coffeescript": ">=2.7.0" } ``` diff --git a/docs/adapters/shell.md b/docs/adapters/shell.md index 5aa8c37a3..b413f3ef8 100644 --- a/docs/adapters/shell.md +++ b/docs/adapters/shell.md @@ -1,5 +1,7 @@ --- -permalink: /docs/adapters/shell/ +title: Shell adapter +layout: layouts/docs.html +permalink: /adapters/shell.html --- # Shell adapter diff --git a/docs/assets/fonts/eot/handsean-webfont.eot b/docs/assets/fonts/eot/handsean-webfont.eot new file mode 100644 index 000000000..77b62e5f9 Binary files /dev/null and b/docs/assets/fonts/eot/handsean-webfont.eot differ diff --git a/docs/assets/fonts/eot/octicons-regular-webfont.eot b/docs/assets/fonts/eot/octicons-regular-webfont.eot new file mode 100644 index 000000000..d03692611 Binary files /dev/null and b/docs/assets/fonts/eot/octicons-regular-webfont.eot differ diff --git a/docs/assets/fonts/eot/style_154042.eot b/docs/assets/fonts/eot/style_154042.eot new file mode 100644 index 000000000..ae2c0b552 Binary files /dev/null and b/docs/assets/fonts/eot/style_154042.eot differ diff --git a/docs/assets/fonts/eot/style_154044.eot b/docs/assets/fonts/eot/style_154044.eot new file mode 100644 index 000000000..5908bcad0 Binary files /dev/null and b/docs/assets/fonts/eot/style_154044.eot differ diff --git a/docs/assets/fonts/eot/style_154045.eot b/docs/assets/fonts/eot/style_154045.eot new file mode 100644 index 000000000..827686f14 Binary files /dev/null and b/docs/assets/fonts/eot/style_154045.eot differ diff --git a/docs/assets/fonts/eot/style_154046.eot b/docs/assets/fonts/eot/style_154046.eot new file mode 100644 index 000000000..e59a97696 Binary files /dev/null and b/docs/assets/fonts/eot/style_154046.eot differ diff --git a/docs/assets/fonts/eot/style_154049.eot b/docs/assets/fonts/eot/style_154049.eot new file mode 100644 index 000000000..d7383e7c7 Binary files /dev/null and b/docs/assets/fonts/eot/style_154049.eot differ diff --git a/docs/assets/fonts/eot/style_154051.eot b/docs/assets/fonts/eot/style_154051.eot new file mode 100644 index 000000000..70f918eaa Binary files /dev/null and b/docs/assets/fonts/eot/style_154051.eot differ diff --git a/docs/assets/fonts/eot/style_154053.eot b/docs/assets/fonts/eot/style_154053.eot new file mode 100644 index 000000000..8fe158823 Binary files /dev/null and b/docs/assets/fonts/eot/style_154053.eot differ diff --git a/docs/assets/fonts/otf/handsean-webfont.ttf b/docs/assets/fonts/otf/handsean-webfont.ttf new file mode 100644 index 000000000..6b7533548 Binary files /dev/null and b/docs/assets/fonts/otf/handsean-webfont.ttf differ diff --git a/docs/assets/fonts/otf/octicons-regular-webfont.otf b/docs/assets/fonts/otf/octicons-regular-webfont.otf new file mode 100644 index 000000000..a7653247c Binary files /dev/null and b/docs/assets/fonts/otf/octicons-regular-webfont.otf differ diff --git a/docs/assets/fonts/otf/style_154042.otf b/docs/assets/fonts/otf/style_154042.otf new file mode 100644 index 000000000..eb72aade2 Binary files /dev/null and b/docs/assets/fonts/otf/style_154042.otf differ diff --git a/docs/assets/fonts/otf/style_154045.otf b/docs/assets/fonts/otf/style_154045.otf new file mode 100644 index 000000000..a89faf181 Binary files /dev/null and b/docs/assets/fonts/otf/style_154045.otf differ diff --git a/docs/assets/fonts/otf/style_154046.otf b/docs/assets/fonts/otf/style_154046.otf new file mode 100644 index 000000000..d070d6584 Binary files /dev/null and b/docs/assets/fonts/otf/style_154046.otf differ diff --git a/docs/assets/fonts/otf/style_154048.otf b/docs/assets/fonts/otf/style_154048.otf new file mode 100644 index 000000000..1e285868e Binary files /dev/null and b/docs/assets/fonts/otf/style_154048.otf differ diff --git a/docs/assets/fonts/otf/style_154051.otf b/docs/assets/fonts/otf/style_154051.otf new file mode 100644 index 000000000..a45a901c6 Binary files /dev/null and b/docs/assets/fonts/otf/style_154051.otf differ diff --git a/docs/assets/fonts/otf/style_154053.otf b/docs/assets/fonts/otf/style_154053.otf new file mode 100644 index 000000000..916f65cb8 Binary files /dev/null and b/docs/assets/fonts/otf/style_154053.otf differ diff --git a/docs/assets/fonts/svg/handsean-webfont.svg b/docs/assets/fonts/svg/handsean-webfont.svg new file mode 100644 index 000000000..401ffa273 --- /dev/null +++ b/docs/assets/fonts/svg/handsean-webfont.svg @@ -0,0 +1,250 @@ + + + + +This is a custom SVG webfont generated by Font Squirrel. +Copyright : Typeface your company 2010 All Rights Reserved +Designer : Sean Johnson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/assets/fonts/svg/octicons-regular-webfont.svg b/docs/assets/fonts/svg/octicons-regular-webfont.svg new file mode 100644 index 000000000..04a1bbdf4 --- /dev/null +++ b/docs/assets/fonts/svg/octicons-regular-webfont.svg @@ -0,0 +1,332 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/assets/fonts/svg/style_154042.svg b/docs/assets/fonts/svg/style_154042.svg new file mode 100644 index 000000000..313012a82 --- /dev/null +++ b/docs/assets/fonts/svg/style_154042.svg @@ -0,0 +1,774 @@ + + +This is a Webfont from MyFonts. Full information about this font: +http://www.myfonts.com/fonts/sudtipos/politica/bold-italic// +Copyright 2004, SUDTIPOS. Disenada por Alejandro Paul y Alfredo Graziani. Todos los derechos reservados.Politica is a trademark of Sudtipos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/fonts/svg/style_154044.svg b/docs/assets/fonts/svg/style_154044.svg new file mode 100644 index 000000000..e66318752 --- /dev/null +++ b/docs/assets/fonts/svg/style_154044.svg @@ -0,0 +1,778 @@ + + +This is a Webfont from MyFonts. Full information about this font: +http://www.myfonts.com/fonts/sudtipos/politica/light-italic// +Copyright 2004, SUDTIPOS. Disenada por Alejandro Paul y Alfredo Graziani. Todos los derechos reservados.Politica is a trademark of Sudtipos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/fonts/svg/style_154045.svg b/docs/assets/fonts/svg/style_154045.svg new file mode 100644 index 000000000..de4ccbe3d --- /dev/null +++ b/docs/assets/fonts/svg/style_154045.svg @@ -0,0 +1,740 @@ + + +This is a Webfont from MyFonts. Full information about this font: +http://www.myfonts.com/fonts/sudtipos/politica/bold// +Copyright 2004, SUDTIPOS. Disenada por Alejandro Paul y Alfredo Graziani. Todos los derechos reservados.Politica is a trademark of Sudtipos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/fonts/svg/style_154046.svg b/docs/assets/fonts/svg/style_154046.svg new file mode 100644 index 000000000..90b65bbde --- /dev/null +++ b/docs/assets/fonts/svg/style_154046.svg @@ -0,0 +1,736 @@ + + +This is a Webfont from MyFonts. Full information about this font: +http://www.myfonts.com/fonts/sudtipos/politica/regular// +Copyright 2004, SUDTIPOS. Disenada por Alejandro Paul y Alfredo Graziani. Todos los derechos reservados.Politica is a trademark of Sudtipos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/fonts/svg/style_154048.svg b/docs/assets/fonts/svg/style_154048.svg new file mode 100644 index 000000000..781d40892 --- /dev/null +++ b/docs/assets/fonts/svg/style_154048.svg @@ -0,0 +1,769 @@ + + +This is a Webfont from MyFonts. Full information about this font: +http://www.myfonts.com/fonts/sudtipos/politica/italic// +Copyright 2004, SUDTIPOS. Disenada por Alejandro Paul y Alfredo Graziani. Todos los derechos reservados.Politica is a trademark of Sudtipos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/fonts/svg/style_154049.svg b/docs/assets/fonts/svg/style_154049.svg new file mode 100644 index 000000000..78e54077b --- /dev/null +++ b/docs/assets/fonts/svg/style_154049.svg @@ -0,0 +1,750 @@ + + +This is a Webfont from MyFonts. Full information about this font: +http://www.myfonts.com/fonts/sudtipos/politica/thin-italic// +Copyright 2004, SUDTIPOS. Disenada por Alejandro Paul y Alfredo Graziani. Todos los derechos reservados.Politica is a trademark of Sudtipos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/fonts/svg/style_154051.svg b/docs/assets/fonts/svg/style_154051.svg new file mode 100644 index 000000000..59bcb9a84 --- /dev/null +++ b/docs/assets/fonts/svg/style_154051.svg @@ -0,0 +1,730 @@ + + +This is a Webfont from MyFonts. Full information about this font: +http://www.myfonts.com/fonts/sudtipos/politica/light// +Copyright 2004, SUDTIPOS. Disenada por Alejandro Paul y Alfredo Graziani. Todos los derechos reservados.Politica is a trademark of Sudtipos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/fonts/svg/style_154053.svg b/docs/assets/fonts/svg/style_154053.svg new file mode 100644 index 000000000..fa5476548 --- /dev/null +++ b/docs/assets/fonts/svg/style_154053.svg @@ -0,0 +1,731 @@ + + +This is a Webfont from MyFonts. Full information about this font: +http://www.myfonts.com/fonts/sudtipos/politica/thin// +Copyright 2004, SUDTIPOS. Disenada por Alejandro Paul y Alfredo Graziani. Todos los derechos reservados.Politica is a trademark of Sudtipos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/fonts/woff/handsean-webfont.woff b/docs/assets/fonts/woff/handsean-webfont.woff new file mode 100644 index 000000000..a88074fd3 Binary files /dev/null and b/docs/assets/fonts/woff/handsean-webfont.woff differ diff --git a/docs/assets/fonts/woff/octicons-regular-webfont.woff b/docs/assets/fonts/woff/octicons-regular-webfont.woff new file mode 100644 index 000000000..2ba436bd5 Binary files /dev/null and b/docs/assets/fonts/woff/octicons-regular-webfont.woff differ diff --git a/docs/assets/fonts/woff/style_154042.woff b/docs/assets/fonts/woff/style_154042.woff new file mode 100644 index 000000000..fbd5b1168 Binary files /dev/null and b/docs/assets/fonts/woff/style_154042.woff differ diff --git a/docs/assets/fonts/woff/style_154044.woff b/docs/assets/fonts/woff/style_154044.woff new file mode 100644 index 000000000..59d33df71 Binary files /dev/null and b/docs/assets/fonts/woff/style_154044.woff differ diff --git a/docs/assets/fonts/woff/style_154045.woff b/docs/assets/fonts/woff/style_154045.woff new file mode 100644 index 000000000..10d1a1fe4 Binary files /dev/null and b/docs/assets/fonts/woff/style_154045.woff differ diff --git a/docs/assets/fonts/woff/style_154046.woff b/docs/assets/fonts/woff/style_154046.woff new file mode 100644 index 000000000..6a0bd8aa8 Binary files /dev/null and b/docs/assets/fonts/woff/style_154046.woff differ diff --git a/docs/assets/fonts/woff/style_154048.woff b/docs/assets/fonts/woff/style_154048.woff new file mode 100644 index 000000000..f7515dd1d Binary files /dev/null and b/docs/assets/fonts/woff/style_154048.woff differ diff --git a/docs/assets/fonts/woff/style_154049.woff b/docs/assets/fonts/woff/style_154049.woff new file mode 100644 index 000000000..127dfa5cf Binary files /dev/null and b/docs/assets/fonts/woff/style_154049.woff differ diff --git a/docs/assets/fonts/woff/style_154051.woff b/docs/assets/fonts/woff/style_154051.woff new file mode 100644 index 000000000..e4f34ee67 Binary files /dev/null and b/docs/assets/fonts/woff/style_154051.woff differ diff --git a/docs/assets/fonts/woff/style_154053.woff b/docs/assets/fonts/woff/style_154053.woff new file mode 100644 index 000000000..2c9927b9c Binary files /dev/null and b/docs/assets/fonts/woff/style_154053.woff differ diff --git a/docs/assets/images/layout/checkbawx.png b/docs/assets/images/layout/checkbawx.png new file mode 100644 index 000000000..f688f5abf Binary files /dev/null and b/docs/assets/images/layout/checkbawx.png differ diff --git a/docs/assets/images/layout/emblem.png b/docs/assets/images/layout/emblem.png new file mode 100644 index 000000000..d8f25bd5e Binary files /dev/null and b/docs/assets/images/layout/emblem.png differ diff --git a/docs/assets/images/layout/emblem@2x.png b/docs/assets/images/layout/emblem@2x.png new file mode 100644 index 000000000..0141b0a88 Binary files /dev/null and b/docs/assets/images/layout/emblem@2x.png differ diff --git a/docs/assets/images/layout/header-field-1.png b/docs/assets/images/layout/header-field-1.png new file mode 100644 index 000000000..5e87e25a5 Binary files /dev/null and b/docs/assets/images/layout/header-field-1.png differ diff --git a/docs/assets/images/layout/header-field-1@2x.png b/docs/assets/images/layout/header-field-1@2x.png new file mode 100644 index 000000000..2415b7bc0 Binary files /dev/null and b/docs/assets/images/layout/header-field-1@2x.png differ diff --git a/docs/assets/images/layout/header-field-2.png b/docs/assets/images/layout/header-field-2.png new file mode 100644 index 000000000..86277a7ab Binary files /dev/null and b/docs/assets/images/layout/header-field-2.png differ diff --git a/docs/assets/images/layout/header-field-2@2x.png b/docs/assets/images/layout/header-field-2@2x.png new file mode 100644 index 000000000..99041b447 Binary files /dev/null and b/docs/assets/images/layout/header-field-2@2x.png differ diff --git a/docs/assets/images/layout/header-field-3.png b/docs/assets/images/layout/header-field-3.png new file mode 100644 index 000000000..054289dbc Binary files /dev/null and b/docs/assets/images/layout/header-field-3.png differ diff --git a/docs/assets/images/layout/header-field-3@2x.png b/docs/assets/images/layout/header-field-3@2x.png new file mode 100644 index 000000000..b854e97fa Binary files /dev/null and b/docs/assets/images/layout/header-field-3@2x.png differ diff --git a/docs/assets/images/layout/header-field-emblem.png b/docs/assets/images/layout/header-field-emblem.png new file mode 100644 index 000000000..08d62d91b Binary files /dev/null and b/docs/assets/images/layout/header-field-emblem.png differ diff --git a/docs/assets/images/layout/header-field-emblem@2x.png b/docs/assets/images/layout/header-field-emblem@2x.png new file mode 100644 index 000000000..a06b27dd2 Binary files /dev/null and b/docs/assets/images/layout/header-field-emblem@2x.png differ diff --git a/docs/assets/images/layout/hubot-avatar@2x.png b/docs/assets/images/layout/hubot-avatar@2x.png new file mode 100644 index 000000000..9783b87e5 Binary files /dev/null and b/docs/assets/images/layout/hubot-avatar@2x.png differ diff --git a/docs/assets/images/layout/lined-paper.png b/docs/assets/images/layout/lined-paper.png new file mode 100644 index 000000000..f8c7ddc69 Binary files /dev/null and b/docs/assets/images/layout/lined-paper.png differ diff --git a/docs/assets/images/layout/lined-paper@2x.png b/docs/assets/images/layout/lined-paper@2x.png new file mode 100644 index 000000000..95274156c Binary files /dev/null and b/docs/assets/images/layout/lined-paper@2x.png differ diff --git a/docs/assets/images/layout/old-mathematics.png b/docs/assets/images/layout/old-mathematics.png new file mode 100644 index 000000000..95285ff60 Binary files /dev/null and b/docs/assets/images/layout/old-mathematics.png differ diff --git a/docs/assets/images/layout/old-mathematics@2x.png b/docs/assets/images/layout/old-mathematics@2x.png new file mode 100644 index 000000000..ce3b7a3de Binary files /dev/null and b/docs/assets/images/layout/old-mathematics@2x.png differ diff --git a/docs/assets/images/layout/project-paper.png b/docs/assets/images/layout/project-paper.png new file mode 100644 index 000000000..66517fbc1 Binary files /dev/null and b/docs/assets/images/layout/project-paper.png differ diff --git a/docs/assets/images/layout/schematic-shadow.png b/docs/assets/images/layout/schematic-shadow.png new file mode 100644 index 000000000..bde246481 Binary files /dev/null and b/docs/assets/images/layout/schematic-shadow.png differ diff --git a/docs/assets/images/layout/schematic.svg b/docs/assets/images/layout/schematic.svg new file mode 100644 index 000000000..9c8607384 --- /dev/null +++ b/docs/assets/images/layout/schematic.svg @@ -0,0 +1,1815 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/images/layout/signature.png b/docs/assets/images/layout/signature.png new file mode 100644 index 000000000..79c753c0b Binary files /dev/null and b/docs/assets/images/layout/signature.png differ diff --git a/docs/assets/images/layout/signature@2x.png b/docs/assets/images/layout/signature@2x.png new file mode 100644 index 000000000..66b3b5716 Binary files /dev/null and b/docs/assets/images/layout/signature@2x.png differ diff --git a/docs/assets/images/layout/tape.png b/docs/assets/images/layout/tape.png new file mode 100644 index 000000000..85b036e3a Binary files /dev/null and b/docs/assets/images/layout/tape.png differ diff --git a/docs/assets/images/screenshots/dangerroom-full.png b/docs/assets/images/screenshots/dangerroom-full.png new file mode 100644 index 000000000..4b2d243c6 Binary files /dev/null and b/docs/assets/images/screenshots/dangerroom-full.png differ diff --git a/docs/assets/stylesheets/application.css b/docs/assets/stylesheets/application.css new file mode 100644 index 000000000..a1d137dd9 --- /dev/null +++ b/docs/assets/stylesheets/application.css @@ -0,0 +1,1529 @@ +@font-face { + font-family: 'octicons'; + src: url("/assets/vendors/octicons/octicons/octicons.eot"); + src: url("/assets/vendors/octicons/octicons/octicons.eot?#iefix") format("embedded-opentype"), + url("/assets/vendors/octicons/octicons/octicons.woff") format("woff"), + url("/assets/vendors/octicons/octicons/octicons.ttf") format("truetype"), + url("/assets/vendors/octicons/octicons/octicons.svg#svgFontName") format("svg"); +} + +* { + box-sizing: border-box; +} + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +nav, +section { + display: block; +} + +audio, +canvas, +video { + display: inline-block; +} + +audio:not([controls]) { + display: none; +} + +[hidden] { + display: none; +} + +html { + font-size: 21px; +} + +body { + margin: 0; + font-size: 0.8rem; + line-height: 1.5; +} + +body, +button, +input, +select, +textarea { + color: #222; +} + +::-moz-selection { + background: #4793bd; + color: #fff; + text-shadow: none; +} + +::selection { + background: #4793bd; + color: #fff; + text-shadow: none; +} + +a { + color: #00e; +} + +a:visited { + color: #551a8b; +} + +a:hover { + color: #06e; +} + +a:focus { + outline: thin dotted; +} + +a:hover, +a:active { + outline: 0; +} + +abbr[title] { + border-bottom: 1px dotted; +} + +b, +strong { + font-weight: bold; +} + +blockquote { + margin: 1em 40px; +} + +dfn { + font-style: italic; +} + +hr { + display: block; + height: 1px; + border: 0; + border-top: 1px solid #ccc; + margin: 1em 0; + padding: 0; +} + +ins { + background: #ff9; + color: #000; + text-decoration: none; +} + +mark { + background: #ff0; + color: #000; + font-style: italic; + font-weight: bold; +} + +pre, +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +pre { + white-space: pre; + white-space: pre-wrap; + word-wrap: normal; + padding: 16px; + overflow: auto; + line-height: 1.45; + background-color: #f7f7f7; + border-radius: 3px; +} + +q { + quotes: none; +} + +q:before, +q:after { + content: ""; + content: none; +} + +small { + font-size: 0.5rem; +} + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +ul, +ol { + margin: 1em 0; + padding: 0 0 0 40px; +} + +dd { + margin: 0 0 0 40px; +} + +nav ul, +nav ol { + list-style: none; + list-style-image: none; + margin: 0; + padding: 0; +} + +img { + border: 0; + -ms-interpolation-mode: bicubic; + vertical-align: middle; +} + +svg:not(:root) { + overflow: hidden; +} + +figure { + margin: 0; +} + +form { + margin: 0; +} + +fieldset { + border: 0; + margin: 0; + padding: 0; +} + +label { + cursor: pointer; +} + +legend { + border: 0; + padding: 0; +} + +button, +input, +select, +textarea { + font-size: 100%; + margin: 0; + vertical-align: baseline; +} + +button, +input { + line-height: normal; +} + +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + cursor: pointer; + appearance: button; +} + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; +} + +input[type="search"] { + appearance: textfield; + box-sizing: content-box; +} + +input[type="search"]::-webkit-search-decoration { + appearance: none; +} + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +textarea { + overflow: auto; + vertical-align: top; + resize: vertical; +} + +input:invalid, +textarea:invalid { + background-color: #f0dddd; +} + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td { + vertical-align: top; +} + +@font-face { + font-family: HandOfSeanRegular; + src: url("/assets/fonts/eot/handsean-webfont.eot"); + src: local("☺"), url("/assets/fonts/otf/handsean-webfont.ttf") format("truetype"), + url("/assets/fonts/svg/handsean-webfont.svg#webfontV6q8jOTr") format("svg"), + url("/assets/fonts/woff/handsean-webfont.woff") format("woff"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: PoliticaThin; + src: url("/assets/fonts/eot/style_154053.eot"); + src: local("☺"), url("/assets/fonts/otf/style_154053.otf") format("opentype"), + url("/assets/fonts/svg/style_154053.svg#PoliticaThin") format("svg"), + url("/assets/fonts/woff/style_154053.woff") format("woff"); +} + +@font-face { + font-family: PoliticaThin-Italic; + src: url("/assets/fonts/eot/style_154049.eot"); + src: local("☺"), url("/assets/fonts/svg/style_154049.svg#PoliticaThin-Italic") format("svg"), + url("/assets/fonts/woff/style_154049.woff") format("woff"); +} + +@font-face { + font-family: PoliticaLight; + src: url("/assets/fonts/eot/style_154051.eot"); + src: local("☺"), url("/assets/fonts/otf/style_154051.otf") format("opentype"), + url("/assets/fonts/svg/style_154051.svg#PoliticaLight") format("svg"), + url("/assets/fonts/woff/style_154051.woff") format("woff"); +} + +@font-face { + font-family: PoliticaLight-Italic; + src: url("/assets/fonts/eot/style_154044.eot"); + src: local("☺"), url("/assets/fonts/svg/style_154044.svg#PoliticaLight-Italic") format("svg"), + url("/assets/fonts/woff/style_154044.woff") format("woff"); +} + +@font-face { + font-family: Politica; + src: url("/assets/fonts/eot/style_154046.eot"); + src: local("☺"), url("/assets/fonts/otf/style_154046.otf") format("opentype"), + url("/assets/fonts/svg/style_154046.svg#Politica") format("svg"), + url("/assets/fonts/woff/style_154046.woff") format("woff"); +} + +@font-face { + font-family: Politica-Italic; + src: url("/assets/fonts/eot/style_154048"); + src: local("☺"), url("/assets/fonts/otf/style_154048.otf") format("opentype"), + url("/assets/fonts/svg/style_154048.svg#Politica-Italic") format("svg"), + url("/assets/fonts/woff/style_154048.woff") format("woff"); +} + +@font-face { + font-family: Politica-Bold; + src: url("/assets/fonts/eot/style_154045.eot"); + src: local("☺"), url("/assets/fonts/otf/style_154045.otf") format("opentype"), + url("/assets/fonts/svg/style_154045.svg#Politica-Bold") format("svg"), + url("/assets/fonts/woff/style_154045.woff") format("woff"); +} + +@font-face { + font-family: Politica-BoldItalic; + src: url("/assets/fonts/eot/style_154042.eot"); + src: local("☺"), url("/assets/fonts/otf/style_154042.otf") format("opentype"), + url("/assets/fonts/svg/style_154042.svg#Politica-BoldItalic") format("svg"), + url("/assets/fonts/woff/style_154042.woff") format("woff"); +} + +@font-face { + font-family: 'Octicons Regular'; + src: url("/assets/fonts/eot/octicons-regular-webfont.eot"); + src: local("☺"), url("/assets/fonts/eot/octicons-regular-webfont.eot#iefix") format("embedded-opentype"), + url("/assets/fonts/otf/octicons-regular-webfont.otf") format("opentype"), + url("/assets/fonts/svg/octicons-regular-webfont.svg#newFontRegular") format("svg"), + url("/assets/fonts/woff/octicons-regular-webfont.woff") format("woff"); +} + +a, +a:visited { + color: #5f8faf; + text-decoration: none; +} + +body { + background: url("/assets/images/layout/project-paper.png"); + font-family: 'Helvetica Neue', Helvetica, arial, freesans, clean, sans-serif; + padding: 1rem; +} + +.container { + background: #fff; + margin: 0 auto; + box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.05), 0 0 0 12px rgba(0, 0, 0, 0.02) inset; +} + +.frame { + background: url("/assets/images/layout/header-field-emblem.png") no-repeat left top; + border: solid 1px #eee; +} +.hubot-avatar { + background-color: #fff; + background-image: linear-gradient(#fff 10%, #f7f7f7 90%); + background-position: center center; + background-repeat: no-repeat; + border-radius: 4px; + height: 106px; + left: 1px; + top: 1px; + width: 106px; + transform: rotate(-3deg); + box-shadow: 0 0 1px transparent, 0 2px 2px rgba(0, 0, 0, 0.2); +} + +.hubot-avatar:before { + background: url("/assets/images/layout/tape.png") no-repeat left top; + content: ''; + display: block; + height: 26px; + position: absolute; + top: -12px; + left: 4px; + width: 99px; + z-index: 1; +} + +.hubot-avatar .hubot-avatar-img { + width: 100%; + position: absolute; + top: 0; + left: 0; + right: 0; + margin: auto; +} + +header.clearfix { + position: relative; + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + gap: 1rem; +} + +header.clearfix h1 { + background: url("/assets/images/layout/header-field-1.png") no-repeat left bottom; + color: #333; + font-family: Politica; + font-size: 72px; + letter-spacing: -5px; + margin: 0 12px; + position: relative; + text-transform: uppercase; + width: 252px; +} +header.clearfix h1 span { + color: #7880a7; + display: block; + font-family: 'HandOfSeanRegular'; + font-size: 14px; + font-weight: normal; + left: 144px; + letter-spacing: -1px; + position: absolute; + top: 32px; + text-transform: lowercase; + width: 125px; + transform: rotate(-3deg); +} + +header.clearfix h1 span b { + font-size: 18px; + font-weight: normal; +} + +header.clearfix h2 { + background: url("/assets/images/layout/header-field-2.png") no-repeat left bottom; + color: #7880a7; + float: left; + font-size: 16px; + font-weight: normal; + height: 132px; + line-height: 18px; + margin: 0 12px; + padding-top: 52px; + text-transform: uppercase; + width: 252px; +} + +header.clearfix p { + background: url("/assets/images/layout/header-field-3.png") no-repeat left bottom; + color: #bebebe; + float: left; + font-size: 16px; + font-weight: normal; + height: 132px; + line-height: 18px; + margin: 0 0 0 12px; + padding-top: 34px; + text-transform: uppercase; + width: 252px; +} + +.download { + line-height: 24px; +} + +.insides { + margin: 12px 0 12px; + display: flex; + flex-wrap: wrap; + justify-content: space-evenly; + gap: 1rem; +} + + +.insides .button { + background: url("/assets/images/layout/checkbawx.png") no-repeat 12px 12px; + border: 1px solid #ddd; + margin: 0; + padding: 10px 12px 10px 30px; + text-transform: uppercase; + display: block; + width: 40%; +} + +@media (max-width: 920px) { + .insides .button { + width: 80%; + } +} + +.insides .button:hover { + background-position: 12px -44px; + border: 1px solid #ccc; + background-color: rgba(254, 174, 40, 0.1); +} + +.insides .button:first-child { + margin-left: 0; +} + +.insides span { + color: #ccc; + display: block; + font-size: 10px; +} + +.main { + position: relative; +} + +.schematics { + background: url("/assets/images/layout/schematic-shadow.png") no-repeat center bottom; + height: 444px; + z-index: 5; + transform: rotate(-1deg); +} + +.schematic { + background: url("/assets/images/layout/old-mathematics.png"); + background-size: auto, 100px; + height: 432px; + position: relative; + box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2); +} + +.schematic .schematic-img { + margin: 10px auto 0; + display: block; + padding-top: 25px; + width: 100%; + height: 100%; +} + +.schematic p { + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.1); + color: #999; + bottom: 12px; + font-size: 10px; + left: 24px; + padding: 6px 12px; + position: absolute; + text-transform: uppercase; + width: 172px; +} + +.about { + color: #666; + line-height: 24px; + padding: 1rem; + width: 100%; + display: flex; + flex-direction: row; + gap: 1rem; + justify-content: space-evenly; + align-items: center; +} +.about article { + width: 70%; +} +.about aside { + width: 30%; +} + +@media (max-width: 920px) { + .about { + flex-direction: column; + } + .about aside, + .about article { + width: 100%; + } +} + +.about h2 { + border-bottom: 1px solid #eee; + padding-bottom: 12px; + color: #222; + font-weight: normal; + margin-top: 24px; +} + +.letter { + background: url("/assets/images/layout/emblem.png") no-repeat 92% 92%, url("/assets/images/layout/signature.png") no-repeat 42px 98%, url("/assets/images/layout/lined-paper.png") no-repeat left top; + line-height: 24px; + padding: 48px 24px 106px 48px; + box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.2); + position: relative; + margin-bottom: 2rem; +} + +.letter:before, +.letter:after { + background: url("/assets/images/layout/tape.png") no-repeat left top; + content: ''; + position: absolute; + width: 99px; + height: 26px; +} + +.letter:before { + top: -12px; +} + +.letter:after { + top: -12px; + right: 32px; +} + +.screenshot { + width: 100%; + text-align: center; + z-index: 60; + position: relative; +} +.screenshot img { + width: 100%; + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.25); +} + +.screenshot:before { + background: url("/assets/images/layout/tape.png") no-repeat left top; + content: ''; + display: block; + height: 26px; + position: absolute; + top: -12px; + left: 50%; + margin-left: -49px; + width: 99px; +} + +.footer { + color: #4c82a5; + margin: 24px auto 0; + padding: 0 24px; +} + +.footer a:visited { + color: #5f8faf; +} + +.footer .right { + margin: 0; + float: right; +} + +.mega-icon { + font-family: 'Octicons Regular'; + font-weight: normal; + font-style: normal; + display: inline-block; + line-height: 1; + -webkit-font-smoothing: antialiased; + text-decoration: none; +} + +.mega-icon-invertocat { + color: #4c82a5; + position: absolute; + left: 50%; + height: 24px; + width: 24px; + margin-top: -4px; + margin-left: -12px; + font-size: 24px; +} + +@media (max-width: 600px) { + .mega-icon-invertocat { + position: static; + } +} + +.mega-icon-invertocat:before { + content: "\f20a"; +} + +.docs .container .main { + display: flex; + padding: 0 2rem; +} + +.docs .container .main h2 { + border-bottom: 1px solid #eee; + padding-bottom: 0.8rem; + color: #222; + font-weight: normal; +} +.docs .container header.clearfix { + display: block; + margin-left: 170px; + position: relative; +} +.docs .container header.clearfix a { + position: absolute; + top: 0; + left: -135px; +} +.docs header h1 { + background: none; + width: auto; + border-bottom: 1px solid #ccc; + margin-bottom: 30px; +} + +.docs-nav { + margin-right: 20px; + margin-top: 20px; + border: 1px solid #ccc; + font-size: 14px; +} + +.docs-nav li { + border-top: 1px solid #ccc; +} + +.docs-nav .subpage .docs-link { + padding-left: 24px; +} + +.docs-nav .docs-list>li:first-child { + border-top: none; +} + +.docs-nav .docs-link { + display: block; + padding: 8px 10px; +} + +.docs-nav .docs-link:hover, +.docs-nav .docs-link.current { + background-color: rgba(254, 174, 40, 0.1); + color: #333; + border-right: 4px solid #feae28; + padding-right: 6px; +} +@font-face { + font-family: 'octicons'; + src: font-url("octicons.eot?#iefix") format("embedded-opentype"), font-url("octicons.woff") format("woff"), font-url("octicons.ttf") format("truetype"), font-url("octicons.svg#octicons") format("svg"); + font-weight: normal; + font-style: normal; +} + +.octicon, +.mega-octicon { + font: normal normal normal 16px/1 octicons; + display: inline-block; + text-decoration: none; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + user-select: none; +} + +.mega-octicon { + font-size: 32px; +} + +.octicon-alert:before { + content: '\f02d'; +} + +.octicon-alignment-align:before { + content: '\f08a'; +} + +.octicon-alignment-aligned-to:before { + content: '\f08e'; +} + +.octicon-alignment-unalign:before { + content: '\f08b'; +} + +.octicon-arrow-down:before { + content: '\f03f'; +} + +.octicon-arrow-left:before { + content: '\f040'; +} + +.octicon-arrow-right:before { + content: '\f03e'; +} + +.octicon-arrow-small-down:before { + content: '\f0a0'; +} + +.octicon-arrow-small-left:before { + content: '\f0a1'; +} + +.octicon-arrow-small-right:before { + content: '\f071'; +} + +.octicon-arrow-small-up:before { + content: '\f09f'; +} + +.octicon-arrow-up:before { + content: '\f03d'; +} + +.octicon-beer:before { + content: '\f069'; +} + +.octicon-book:before { + content: '\f007'; +} + +.octicon-bookmark:before { + content: '\f07b'; +} + +.octicon-briefcase:before { + content: '\f0d3'; +} + +.octicon-broadcast:before { + content: '\f048'; +} + +.octicon-browser:before { + content: '\f0c5'; +} + +.octicon-bug:before { + content: '\f091'; +} + +.octicon-calendar:before { + content: '\f068'; +} + +.octicon-check:before { + content: '\f03a'; +} + +.octicon-checklist:before { + content: '\f076'; +} + +.octicon-chevron-down:before { + content: '\f0a3'; +} + +.octicon-chevron-left:before { + content: '\f0a4'; +} + +.octicon-chevron-right:before { + content: '\f078'; +} + +.octicon-chevron-up:before { + content: '\f0a2'; +} + +.octicon-circle-slash:before { + content: '\f084'; +} + +.octicon-circuit-board:before { + content: '\f0d6'; +} + +.octicon-clippy:before { + content: '\f035'; +} + +.octicon-clock:before { + content: '\f046'; +} + +.octicon-cloud-download:before { + content: '\f00b'; +} + +.octicon-cloud-upload:before { + content: '\f00c'; +} + +.octicon-code:before { + content: '\f05f'; +} + +.octicon-color-mode:before { + content: '\f065'; +} + +.octicon-comment-add:before, +.octicon-comment:before { + content: '\f02b'; +} + +.octicon-comment-discussion:before { + content: '\f04f'; +} + +.octicon-credit-card:before { + content: '\f045'; +} + +.octicon-dash:before { + content: '\f0ca'; +} + +.octicon-dashboard:before { + content: '\f07d'; +} + +.octicon-database:before { + content: '\f096'; +} + +.octicon-device-camera:before { + content: '\f056'; +} + +.octicon-device-camera-video:before { + content: '\f057'; +} + +.octicon-device-desktop:before { + content: '\f27c'; +} + +.octicon-device-mobile:before { + content: '\f038'; +} + +.octicon-diff:before { + content: '\f04d'; +} + +.octicon-diff-added:before { + content: '\f06b'; +} + +.octicon-diff-ignored:before { + content: '\f099'; +} + +.octicon-diff-modified:before { + content: '\f06d'; +} + +.octicon-diff-removed:before { + content: '\f06c'; +} + +.octicon-diff-renamed:before { + content: '\f06e'; +} + +.octicon-ellipsis:before { + content: '\f09a'; +} + +.octicon-eye-unwatch:before, +.octicon-eye-watch:before, +.octicon-eye:before { + content: '\f04e'; +} + +.octicon-file-binary:before { + content: '\f094'; +} + +.octicon-file-code:before { + content: '\f010'; +} + +.octicon-file-directory:before { + content: '\f016'; +} + +.octicon-file-media:before { + content: '\f012'; +} + +.octicon-file-pdf:before { + content: '\f014'; +} + +.octicon-file-submodule:before { + content: '\f017'; +} + +.octicon-file-symlink-directory:before { + content: '\f0b1'; +} + +.octicon-file-symlink-file:before { + content: '\f0b0'; +} + +.octicon-file-text:before { + content: '\f011'; +} + +.octicon-file-zip:before { + content: '\f013'; +} + +.octicon-flame:before { + content: '\f0d2'; +} + +.octicon-fold:before { + content: '\f0cc'; +} + +.octicon-gear:before { + content: '\f02f'; +} + +.octicon-gift:before { + content: '\f042'; +} + +.octicon-gist:before { + content: '\f00e'; +} + +.octicon-gist-secret:before { + content: '\f08c'; +} + +.octicon-git-branch-create:before, +.octicon-git-branch-delete:before, +.octicon-git-branch:before { + content: '\f020'; +} + +.octicon-git-commit:before { + content: '\f01f'; +} + +.octicon-git-compare:before { + content: '\f0ac'; +} + +.octicon-git-merge:before { + content: '\f023'; +} + +.octicon-git-pull-request-abandoned:before, +.octicon-git-pull-request:before { + content: '\f009'; +} + +.octicon-globe:before { + content: '\f0b6'; +} + +.octicon-graph:before { + content: '\f043'; +} + +.octicon-heart:before { + content: '\2665'; +} + +.octicon-history:before { + content: '\f07e'; +} + +.octicon-home:before { + content: '\f08d'; +} + +.octicon-horizontal-rule:before { + content: '\f070'; +} + +.octicon-hourglass:before { + content: '\f09e'; +} + +.octicon-hubot:before { + content: '\f09d'; +} + +.octicon-inbox:before { + content: '\f0cf'; +} + +.octicon-info:before { + content: '\f059'; +} + +.octicon-issue-closed:before { + content: '\f028'; +} + +.octicon-issue-opened:before { + content: '\f026'; +} + +.octicon-issue-reopened:before { + content: '\f027'; +} + +.octicon-jersey:before { + content: '\f019'; +} + +.octicon-jump-down:before { + content: '\f072'; +} + +.octicon-jump-left:before { + content: '\f0a5'; +} + +.octicon-jump-right:before { + content: '\f0a6'; +} + +.octicon-jump-up:before { + content: '\f073'; +} + +.octicon-key:before { + content: '\f049'; +} + +.octicon-keyboard:before { + content: '\f00d'; +} + +.octicon-law:before { + content: '\f0d8'; +} + +.octicon-light-bulb:before { + content: '\f000'; +} + +.octicon-link:before { + content: '\f05c'; +} + +.octicon-link-external:before { + content: '\f07f'; +} + +.octicon-list-ordered:before { + content: '\f062'; +} + +.octicon-list-unordered:before { + content: '\f061'; +} + +.octicon-location:before { + content: '\f060'; +} + +.octicon-gist-private:before, +.octicon-mirror-private:before, +.octicon-git-fork-private:before, +.octicon-lock:before { + content: '\f06a'; +} + +.octicon-logo-github:before { + content: '\f092'; +} + +.octicon-mail:before { + content: '\f03b'; +} + +.octicon-mail-read:before { + content: '\f03c'; +} + +.octicon-mail-reply:before { + content: '\f051'; +} + +.octicon-mark-github:before { + content: '\f00a'; +} + +.octicon-markdown:before { + content: '\f0c9'; +} + +.octicon-megaphone:before { + content: '\f077'; +} + +.octicon-mention:before { + content: '\f0be'; +} + +.octicon-microscope:before { + content: '\f089'; +} + +.octicon-milestone:before { + content: '\f075'; +} + +.octicon-mirror-public:before, +.octicon-mirror:before { + content: '\f024'; +} + +.octicon-mortar-board:before { + content: '\f0d7'; +} + +.octicon-move-down:before { + content: '\f0a8'; +} + +.octicon-move-left:before { + content: '\f074'; +} + +.octicon-move-right:before { + content: '\f0a9'; +} + +.octicon-move-up:before { + content: '\f0a7'; +} + +.octicon-mute:before { + content: '\f080'; +} + +.octicon-no-newline:before { + content: '\f09c'; +} + +.octicon-octoface:before { + content: '\f008'; +} + +.octicon-organization:before { + content: '\f037'; +} + +.octicon-package:before { + content: '\f0c4'; +} + +.octicon-paintcan:before { + content: '\f0d1'; +} + +.octicon-pencil:before { + content: '\f058'; +} + +.octicon-person-add:before, +.octicon-person-follow:before, +.octicon-person:before { + content: '\f018'; +} + +.octicon-pin:before { + content: '\f041'; +} + +.octicon-playback-fast-forward:before { + content: '\f0bd'; +} + +.octicon-playback-pause:before { + content: '\f0bb'; +} + +.octicon-playback-play:before { + content: '\f0bf'; +} + +.octicon-playback-rewind:before { + content: '\f0bc'; +} + +.octicon-plug:before { + content: '\f0d4'; +} + +.octicon-repo-create:before, +.octicon-gist-new:before, +.octicon-file-directory-create:before, +.octicon-file-add:before, +.octicon-plus:before { + content: '\f05d'; +} + +.octicon-podium:before { + content: '\f0af'; +} + +.octicon-primitive-dot:before { + content: '\f052'; +} + +.octicon-primitive-square:before { + content: '\f053'; +} + +.octicon-pulse:before { + content: '\f085'; +} + +.octicon-puzzle:before { + content: '\f0c0'; +} + +.octicon-question:before { + content: '\f02c'; +} + +.octicon-quote:before { + content: '\f063'; +} + +.octicon-radio-tower:before { + content: '\f030'; +} + +.octicon-repo-delete:before, +.octicon-repo:before { + content: '\f001'; +} + +.octicon-repo-clone:before { + content: '\f04c'; +} + +.octicon-repo-force-push:before { + content: '\f04a'; +} + +.octicon-gist-fork:before, +.octicon-repo-forked:before { + content: '\f002'; +} + +.octicon-repo-pull:before { + content: '\f006'; +} + +.octicon-repo-push:before { + content: '\f005'; +} + +.octicon-rocket:before { + content: '\f033'; +} + +.octicon-rss:before { + content: '\f034'; +} + +.octicon-ruby:before { + content: '\f047'; +} + +.octicon-screen-full:before { + content: '\f066'; +} + +.octicon-screen-normal:before { + content: '\f067'; +} + +.octicon-search-save:before, +.octicon-search:before { + content: '\f02e'; +} + +.octicon-server:before { + content: '\f097'; +} + +.octicon-settings:before { + content: '\f07c'; +} + +.octicon-log-in:before, +.octicon-sign-in:before { + content: '\f036'; +} + +.octicon-log-out:before, +.octicon-sign-out:before { + content: '\f032'; +} + +.octicon-split:before { + content: '\f0c6'; +} + +.octicon-squirrel:before { + content: '\f0b2'; +} + +.octicon-star-add:before, +.octicon-star-delete:before, +.octicon-star:before { + content: '\f02a'; +} + +.octicon-steps:before { + content: '\f0c7'; +} + +.octicon-stop:before { + content: '\f08f'; +} + +.octicon-repo-sync:before, +.octicon-sync:before { + content: '\f087'; +} + +.octicon-tag-remove:before, +.octicon-tag-add:before, +.octicon-tag:before { + content: '\f015'; +} + +.octicon-telescope:before { + content: '\f088'; +} + +.octicon-terminal:before { + content: '\f0c8'; +} + +.octicon-three-bars:before { + content: '\f05e'; +} + +.octicon-tools:before { + content: '\f031'; +} + +.octicon-trashcan:before { + content: '\f0d0'; +} + +.octicon-triangle-down:before { + content: '\f05b'; +} + +.octicon-triangle-left:before { + content: '\f044'; +} + +.octicon-triangle-right:before { + content: '\f05a'; +} + +.octicon-triangle-up:before { + content: '\f0aa'; +} + +.octicon-unfold:before { + content: '\f039'; +} + +.octicon-unmute:before { + content: '\f0ba'; +} + +.octicon-versions:before { + content: '\f064'; +} + +.octicon-remove-close:before, +.octicon-x:before { + content: '\f081'; +} + +.octicon-zap:before { + content: '\26A1'; +} \ No newline at end of file diff --git a/docs/assets/vendor/octicons/octicons.eot b/docs/assets/vendor/octicons/octicons.eot new file mode 100644 index 000000000..89f55a315 Binary files /dev/null and b/docs/assets/vendor/octicons/octicons.eot differ diff --git a/docs/assets/vendor/octicons/octicons.svg b/docs/assets/vendor/octicons/octicons.svg new file mode 100644 index 000000000..ea3e0f161 --- /dev/null +++ b/docs/assets/vendor/octicons/octicons.svg @@ -0,0 +1,198 @@ + + + + +(c) 2012-2014 GitHub + +When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos) + +Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL) +Applies to all font files + +Code License: MIT (http://choosealicense.com/licenses/mit/) +Applies to all other files + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/assets/vendor/octicons/octicons.ttf b/docs/assets/vendor/octicons/octicons.ttf new file mode 100644 index 000000000..557b893dc Binary files /dev/null and b/docs/assets/vendor/octicons/octicons.ttf differ diff --git a/docs/assets/vendor/octicons/octicons.woff b/docs/assets/vendor/octicons/octicons.woff new file mode 100644 index 000000000..3c0e36ad6 Binary files /dev/null and b/docs/assets/vendor/octicons/octicons.woff differ diff --git a/docs/deploying.md b/docs/deploying.md index 4d725d38d..bed15a2cc 100644 --- a/docs/deploying.md +++ b/docs/deploying.md @@ -1,11 +1,13 @@ --- -permalink: /docs/deploying/ +title: Deploying +layout: layouts/docs.html +permalink: /deploying.html --- # Deploying -- [Azure](./deploying/azure.md) -- [Bluemix](./deploying/bluemix.md) -- [Heroku](./deploying/heroku.md) -- [Unix](./deploying/unix.md) -- [Windows](./deploying/windows.md) +- [Azure](./deploying/azure.html) +- [Bluemix](./deploying/bluemix.html) +- [Heroku](./deploying/heroku.html) +- [Unix](./deploying/unix.html) +- [Windows](./deploying/windows.html) diff --git a/docs/deploying/azure.md b/docs/deploying/azure.md index b5c72cf17..15f0efdde 100644 --- a/docs/deploying/azure.md +++ b/docs/deploying/azure.md @@ -1,11 +1,13 @@ --- -permalink: /docs/deploying/azure/ +title: Deploying to Azure +layout: layouts/docs.html +permalink: /deploying/azure.html --- # Deploying to Azure -If you've been following along with [Getting Started](../index.md), it's time to deploy so you can use it beyond just your local machine. -[Azure](http://azure.microsoft.com/) is a way to deploy hubot as an alternative to [Heroku](heroku.md). +If you've been following along with [Getting Started](../index.html), it's time to deploy so you can use it beyond just your local machine. +[Azure](http://azure.microsoft.com/) is a way to deploy hubot as an alternative to [Heroku](heroku.html). You will need to install the azure-cli via npm after you have follow the initial instructions for your hubot. @@ -69,7 +71,7 @@ Finally, add one more environment variables to your website. You can do this eit % $settings["HUBOT_BRAIN_AZURE_CONNSTRING"] = "your Azure blob storage connection string" % Set-AzureWebsite -AppSettings $settings mynewhubot -Now any scripts that require a brain will function. You should look up other scripts or write your own by looking at the [documentation](../scripting.md). All of the normal scripts for hubot are compatible with hosting hubot on Azure. +Now any scripts that require a brain will function. You should look up other scripts or write your own by looking at the [documentation](../scripting.html). All of the normal scripts for hubot are compatible with hosting hubot on Azure. ### Troubleshooting tips and tricks diff --git a/docs/deploying/bluemix.md b/docs/deploying/bluemix.md index 1e10909df..0b37d0cea 100644 --- a/docs/deploying/bluemix.md +++ b/docs/deploying/bluemix.md @@ -1,13 +1,15 @@ --- -permalink: /docs/deploying/bluemix/ +title: Deploying to Bluemix +layout: layouts/docs.html +permalink: /deploying/bluemix.html --- # Deploying to Bluemix -If you've been following along with [Getting Started](../index.md), it's time +If you've been following along with [Getting Started](../index.html), it's time to deploy so you can use it beyond just your local machine. [IBM Bluemix](http://bluemix.net) is a way to deploy hubot as an alternative to -[Heroku](heroku.md). It is built on the open-source project +[Heroku](heroku.html). It is built on the open-source project [Cloud Foundry](https://www.cloudfoundry.org/), so we'll be using the `cf cli` throughout these examples. diff --git a/docs/deploying/heroku.md b/docs/deploying/heroku.md index 73ca5c0f5..8bd47d4a2 100644 --- a/docs/deploying/heroku.md +++ b/docs/deploying/heroku.md @@ -1,10 +1,12 @@ --- -permalink: /docs/deploying/heroku/ +title: Deploying to Heroku +layout: layouts/docs.html +permalink: /deploying/heroku.html --- # Deploying to Heroku -If you've been following along with [Getting Started](../index.md), it's time to deploy so you can use it beyond just your local machine. +If you've been following along with [Getting Started](../index.html), it's time to deploy so you can use it beyond just your local machine. [Heroku](http://www.heroku.com/) is an easy and supported way to deploy hubot. Install the [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli) to start, then follow their '[Getting Started](https://devcenter.heroku.com/articles/heroku-cli#getting-started)' instructions, including logging in the first time: @@ -33,7 +35,7 @@ Then create a Heroku application: Before you deploy the application, you'll need to configure some environment variables for hubot to use. The specific variables you'll need depends on which -[adapter](../adapters.md) and scripts you are using. For Campfire, with no other +[adapter](../adapters.html) and scripts you are using. For Campfire, with no other scripts, you'd need to set the following environment variables: % heroku config:set HUBOT_CAMPFIRE_ACCOUNT=yourcampfireaccount diff --git a/docs/deploying/unix.md b/docs/deploying/unix.md index f9cbafdbb..53bf9422e 100644 --- a/docs/deploying/unix.md +++ b/docs/deploying/unix.md @@ -1,5 +1,7 @@ --- -permalink: /docs/deploying/unix/ +title: Deploying to Unix +layout: layouts/docs.html +permalink: /deploying/unix.html --- # Deploying to Unix @@ -24,7 +26,7 @@ for [installing Node.js via package manager](https://github.com/joyent/node/wiki ## Updating code on the server The simplest way to update your hubot's code is going to be to have a git -checkout of your hubot's source code (that you've created during [Getting Started](../index.md), not the [github/hubot repository](http://github.com/github/hubot), and just git pull to get change. This may +checkout of your hubot's source code (that you've created during [Getting Started](../index.html), not the [github/hubot repository](http://github.com/github/hubot), and just git pull to get change. This may feel a dirty hack, but it works when you are starting out. If you have a Ruby background, you might be more comfortable using diff --git a/docs/deploying/windows.md b/docs/deploying/windows.md index 9fc39d69b..4d70193f3 100644 --- a/docs/deploying/windows.md +++ b/docs/deploying/windows.md @@ -1,5 +1,7 @@ --- -permalink: /docs/deploying/windows/ +title: Deploying to Windows +layout: layouts/docs.html +permalink: /deploying/windows.html --- # Deploying to Windows @@ -23,7 +25,7 @@ Your other option is to install directly from [NodeJS](https://nodejs.org/) and ## Updating code on the server -To get the code on your server, you can follow the instructions at [Getting Started](../index.md) on your local development machine or directly on the server. If you are building locally, push your hubot to GitHub and clone the repo onto your server. Don't clone the normal [github/hubot repository](http://github.com/github/hubot), make sure you're using the Yo Generator to build your own hubot. +To get the code on your server, you can follow the instructions at [Getting Started](../index.html) on your local development machine or directly on the server. If you are building locally, push your hubot to GitHub and clone the repo onto your server. Don't clone the normal [github/hubot repository](http://github.com/github/hubot), make sure you're using `npx hubot --create myhubot` to build your own hubot. ## Setting up environment vars diff --git a/docs/index.md b/docs/docs.md similarity index 68% rename from docs/index.md rename to docs/docs.md index 13810f763..5f6702f87 100644 --- a/docs/index.md +++ b/docs/docs.md @@ -1,29 +1,31 @@ --- -permalink: /docs/ +title: Getting Started With Hubot +layout: layouts/docs.html +published: 2023-10-10T19:25:22.000Z +permalink: /docs.html --- -## Getting Started With Hubot +# Getting Started With Hubot -You will need [node.js and npm](https://docs.npmjs.com/getting-started/installing-node). Once those are installed, you can setup a new codebase for a new Hubot instance with the following shell commands: +You will need [node.js and npm](https://docs.npmjs.com/getting-started/installing-node). Once those are installed, you can setup a new codebase for a new Hubot instance with the following shell commands. It will create a new directory called `myhubot` in the current working directory. ```sh -mkdir myhubot -cd myhubot -npm init -y -npm i hubot coffeescript +npx hubot --create myhubot ``` Now open `package.json` in your code editor and add a `start` property to the `scripts` property: ```json +{ ... -"scripts": { - "start": "hubot" -} + "scripts": { + "start": "hubot" + } ... +} ``` -Start your Hubot instance by executing `npm start`. It will start with the built in [shell adapter](./adapters/shell.md), which starts a [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) where you can type commands. +Start your Hubot instance by executing `npm start`. It will start with the built in [shell adapter](/adapters/shell.html), which starts a [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) where you can type commands. Your terminal should look like: @@ -46,40 +48,40 @@ Hubot> Changing your Hubot instances name will reduce confusion down the road, so set the `--name` argument in the `hubot` command: ```json +{ ... -"scripts": { - "start": "hubot --name sam" -} + "scripts": { + "start": "hubot --name sam" + } ... +} ``` -Your hubot will now respond as `sam`. Note, a common usage pattern is prefixing a command message with Hubot's name. Hubot's code pattern matches on it in order to trigger sending it to Hubot. This is -case-insensitive, and can be prefixed with `@` or suffixed with `:`. These are equivalent: +Your hubot will now respond as `sam`. Note, a common usage pattern is prefixing a command message with Hubot's name. Hubot's code pattern matches on it in order to trigger sending it to Hubot. This is case-insensitive, and can be prefixed with `@` or suffixed with `:`. The following examples result in the message getting sent to Hubot. -``` +```sh sam> SAM help sam> sam help sam> @sam help sam> sam: help ``` -## Scripts +## Scripts Hubot's power comes through scripts. There are hundreds of scripts written and maintained by the community. Find them by searching the [NPM registry](https://www.npmjs.com/browse/keyword/hubot-scripts) for `hubot-scripts `. For example: -``` +```sh $ npm search hubot-scripts github NAME DESCRIPTION hubot-deployer Giving Hubot the ability to deploy GitHub repos to PaaS providers hubot hubot-scripts hubot-gith hubot-gh-release-pr A hubot script to create GitHub's PR for release hubot-github Giving Hubot the ability to be a vital member of your github organization -… ``` To use a script from an NPM package: 1. Run `npm install ` in the codebase directory to install it. -2. Add the package name to `external-scripts.json`. +2. Add the package name to a file called `external-scripts.json`. ```json ["hubot-diagnostics", "hubot-help"] @@ -87,21 +89,21 @@ To use a script from an NPM package: 3. Run `npm home ` to open a browser window for the homepage of the script, where you can find more information about configuring and installing the script. -You can also create your own scripts and save them in a folder called `./scripts/` (`./` means current working directory) in your codebase directory. All scripts (files ending with `.js`) placed there are automatically loaded and ready to use with your hubot. Read more about customizing hubot by [writing your own scripts](scripting.md). +You can also create your own scripts and save them in a folder called `./scripts/` (`./` means current working directory) in your codebase directory. All scripts (files ending with `.js` and `.mjs`) placed there are automatically loaded and ready to use with your hubot. Read more about customizing hubot by [writing your own scripts](scripting.html). ## Adapters -Hubot uses the adapter pattern to support multiple chat-backends. Here is a [list of available adapters](adapters.md), along with details on how to configure them. Please note that Hubot is undergoing major changes and old adapters may no longer work with the latest version of Hubot (anything after 3.5). +Hubot uses the adapter pattern to support multiple chat-backends. Here is a [list of available adapters](adapters.html), along with details on how to configure them. Please note that Hubot is undergoing major changes and old adapters may no longer work with the latest version of Hubot (anything after 3.5). ## Deploying You can deploy hubot to Heroku, which is the officially supported method. Additionally you are able to deploy hubot to a UNIX-like system or Windows. Please note the support for deploying to Windows isn't officially supported. -* [Deploying Hubot onto Azure](./deploying/azure.md) -* [Deploying Hubot onto Bluemix](./deploying/bluemix.md) -* [Deploying Hubot onto Heroku](./deploying/heroku.md) -* [Deploying Hubot onto Unix](./deploying/unix.md) -* [Deploying Hubot onto Windows](./deploying/windows.md) +* [Deploying Hubot onto Azure](./deploying/azure.html) +* [Deploying Hubot onto Bluemix](./deploying/bluemix.html) +* [Deploying Hubot onto Heroku](./deploying/heroku.html) +* [Deploying Hubot onto Unix](./deploying/unix.html) +* [Deploying Hubot onto Windows](./deploying/windows.html) ## Redis @@ -128,4 +130,4 @@ or ## Patterns -Using custom scripts, you can quickly customize Hubot to be the most life embettering robot he or she can be. Read [docs/patterns.md](patterns.md) for some nifty tricks that may come in handy as you teach your hubot new skills. \ No newline at end of file +Using custom scripts, you can quickly customize Hubot to be the most life embettering robot he or she can be. Read [docs/patterns](patterns.html) for some nifty tricks that may come in handy as you teach your hubot new skills. \ No newline at end of file diff --git a/docs/implementation.md b/docs/implementation.md index f2041f514..e8e8f7e90 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -1,6 +1,7 @@ --- title: Implementation Notes -permalink: /docs/implementation/ +layout: layouts/docs.html +permalink: /implementation.html --- # Implementation diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 000000000..39fc35935 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,50 @@ +{{#> layouts/main.html}} + + +
+
+ +
+
+ hubot schematic +

Fig. 1 — Hubot Schematics

+
+
+
+
+
+

What is Hubot?

+

Hubot is your friendly robot sidekick. Install him in your company to dramatically improve employee efficiency.

+ +

No seriously, what is Hubot?

+

GitHub, Inc., wrote the first version of Hubot to automate our company chat room. Hubot knew how to deploy the site, automate a lot of tasks, and be a source of fun around the office. Eventually he grew to become a formidable force in GitHub, but he led a private, messy life. So we rewrote him.

+

Today's version of Hubot is open source, written in CoffeeScript on Node.js, and easily deployed on platforms like Heroku. More importantly, Hubot is a standardized way to share scripts between everyone's robots.

+ +

What can Hubot do?

+

We ship Hubot with a small group of core scripts: things like posting images, translating languages, and integrating with Google Maps. We also maintain a repository of community Hubot scripts and an organization of community Hubot packages that you can add to your own robot.

+

The real fun happens when you add your own scripts. Be sure to personalize your Hubot, too; your company's robot should be a place full of inside jokes, custom integrations, and general merriment.

+ +

How do I write my own Hubot scripts?

+

Check out this documentation for writing your own Hubot scripts. Then the sky's the limit; just add them to your generated `scripts` directory.

+

If you write a Hubot script for taking over the world, please let us know.

+
+ +
+
+{{/layouts/main.html}} \ No newline at end of file diff --git a/docs/layouts/docs.html b/docs/layouts/docs.html new file mode 100644 index 000000000..f801656a1 --- /dev/null +++ b/docs/layouts/docs.html @@ -0,0 +1,83 @@ + + + + + + + {{ title }} + + + + + + + + + + + + +
+
+ +
+ hubot logo +
+
+

Hubot Documentation

+
+
+ +
+ {{> @partial-block }} +
+
+
+ + + + \ No newline at end of file diff --git a/docs/layouts/main.html b/docs/layouts/main.html new file mode 100644 index 000000000..9032f0505 --- /dev/null +++ b/docs/layouts/main.html @@ -0,0 +1,40 @@ + + + + + + + {{ title }} + + + + + + + + + + + + +
+
+
+
+ hubot logo +
+
+

Hubot (note: it's prounounced hew-bot)

+

A Customizable,
Life Embetterment Robot

+

Commissioned by

+
+ {{> @partial-block }} +
+
+ +

Built with <3 by friends of Hubot

+
+ + \ No newline at end of file diff --git a/docs/patterns.md b/docs/patterns.md index 70b2a0fad..5e78118bd 100644 --- a/docs/patterns.md +++ b/docs/patterns.md @@ -1,5 +1,9 @@ --- -permalink: /docs/patterns/ +title: Patterns +layout: layouts/docs.html +should_publish: yes +published: 2023-10-10T19:25:22.000Z +permalink: /patterns.html --- # Patterns @@ -15,7 +19,7 @@ When you rename Hubot, he will no longer respond to his former name. In order to Setting this up is very easy: -1. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `rename-hubot.js` +1. Create a [bundled script](scripting.html) in the `scripts/` directory of your Hubot instance called `rename-hubot.js` 2. Add the following code, modified for your needs: ```javascript @@ -49,7 +53,7 @@ This pattern is similar to the Renaming the Hubot Instance pattern above: Here is the setup: -1. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `deprecations.js` +1. Create a [bundled script](scripting.html) in the `scripts/` directory of your Hubot instance called `deprecations.js` 2. Copy any old command listeners and add them to that file. For example, if you were to rename the help command for some silly reason: ```javascript @@ -71,7 +75,7 @@ module.exports = (robot) => { Sometimes you have scripts that take several minutes to execute. If these scripts are doing something that could be interfered with by running subsequent commands, you may wish to code your scripts to prevent concurrent access. -To do this, you can set up a lock in the Hubot [brain](scripting.md#persistence) object. The lock is set up here so that different scripts can share the same lock if necessary. +To do this, you can set up a lock in the Hubot [brain](scripting.html#persistence) object. The lock is set up here so that different scripts can share the same lock if necessary. Setting up the lock looks something like this: @@ -106,7 +110,7 @@ In many corporate environments, a web proxy is required to access the Internet a Due to the way Node.js handles HTTP and HTTPS requests, you need to specify a different Agent for each protocol. ScopedHTTPClient will then automatically choose the right ProxyAgent for each request. 1. Install ProxyAgent. `npm install proxy-agent` -2. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `proxy.js` +2. Create a [bundled script](scripting.html) in the `scripts/` directory of your Hubot instance called `proxy.js` 3. Add the following code, modified for your needs: ```javascript diff --git a/docs/scripting.md b/docs/scripting.md index cf9b7b2ce..c9a7ed3de 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -1,23 +1,35 @@ --- -permalink: /docs/scripting/ +title: Scripting +layout: layouts/docs.html +should_publish: yes +published: 2023-10-10T19:25:22.000Z +permalink: /scripting.html --- # Scripting -Hubot out of the box doesn't do too much, but it is an extensible, scriptable robot friend. There are [hundreds of scripts written and maintained by the community](index.md#scripts) and it's easy to write your own. You can create a custom script in Hubot's `scripts` directory or [create a script package](#creating-a-script-package) for sharing with the community! +Hubot out of the box doesn't do too much, but it is an extensible, scriptable robot friend. There are [hundreds of scripts written and maintained by the community](docs.html#scripts) and it's easy to write your own. You can create a custom script in Hubot's `scripts` directory or [create a script package](#creating-a-script-package) for sharing with the community! ## Anatomy of a script When you created your Hubot, the generator also created a `scripts` directory. If you peek around there, you will see some examples. For a script to be a script, it needs to: * live in a directory on the Hubot script load path (`src/scripts` and `scripts` by default) -* be a `.js` file +* be a `.js` or `.mjs` file * export a function whos signature takes 1 parameter (`robot`) By export a function, we just mean: ```javascript -module.exports = (robot) => { +// .mjs +export default async robot => { + // your code here +} +``` + +```javascript +// .js +module.exports = async robot => { // your code here } ``` @@ -29,12 +41,13 @@ The `robot` parameter is an instance of your robot friend. At this point, we can Since this is a chat bot, the most common interactions are based on messages. Hubot can `hear` messages said in a room or `respond` to messages directly addressed at it. Both methods take a regular expression and a callback function as parameters. For example: ```javascript -module.exports = (robot) => { - robot.hear(/badger/i, (res) => { +// .mjs +export default async robot => { + robot.hear(/badger/i, async res => { // your code here }) - robot.respond(/open the pod bay doors/i, (res) => { + robot.respond(/open the pod bay doors/i, async res => { // your code here } } @@ -65,16 +78,17 @@ It wouldn't be called for: The `res` parameter is an instance of `Response` (historically, this parameter was `msg` and you may see other scripts use it this way). With it, you can `send` a message back to the room the `res` came from, `emote` a message to a room (If the given adapter supports it), or `reply` to the person that sent the message. For example: ```javascript -module.exports = (robot) => { - robot.hear(/badger/i, (res) => { +// .mjs +export default async robot => { + robot.hear(/badger/i, async res => { res.send(`Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS`) } - robot.respond(/open the pod bay doors/i, (res) => { + robot.respond(/open the pod bay doors/i, async res => { res.reply(`I'm afraid I can't let you do that.`) } - robot.hear(/I like pie/i, (res) => { + robot.hear(/I like pie/i, async res => { res.emote('makes a freshly baked pie') } } @@ -89,10 +103,11 @@ If a user Dave says "HAL: open the pod bay doors", `robot.respond(/open the pod Messages can be sent to a specified room or user using the messageRoom function. ```javascript -module.exports = (robot) => { - robot.hear(/green eggs/i, (res) => { +// .mjs +export default async robot => { + robot.hear(/green eggs/i, async res => { const room = 'mytestroom' - robot.messageRoom(room, 'I do not like green eggs and ham. I do not like them Sam-I-Am.') + await robot.messageRoom(room, 'I do not like green eggs and ham. I do not like them Sam-I-Am.') } } ``` @@ -100,24 +115,24 @@ module.exports = (robot) => { User name can be explicitely specified if desired ( for a cc to an admin/manager), or using the response object a private message can be sent to the original sender. ```javascript - robot.respond(/I don't like sam-i-am/i, (res) => { + robot.respond(/I don't like sam-i-am/i, async res => { const room = 'joemanager' - robot.messageRoom(room, 'Someone does not like Dr. Seus') - res.reply('That Sam-I-Am\nThat Sam-I-Am\nI do not like\nthat Sam-I-Am') + await robot.messageRoom(room, 'Someone does not like Dr. Seus') + await res.reply('That Sam-I-Am\nThat Sam-I-Am\nI do not like\nthat Sam-I-Am') } - robot.hear(/Sam-I-Am/i, (res) => { + robot.hear(/Sam-I-Am/i, async res => { const room = res.envelope.user.name - robot.messageRoom(room, 'That Sam-I-Am\nThat Sam-I-Am\nI do not like\nthat Sam-I-Am') + await robot.messageRoom(room, 'That Sam-I-Am\nThat Sam-I-Am\nI do not like\nthat Sam-I-Am') } ``` ## Capturing data -So far, our scripts have had static responses, which while amusing, are boring functionality-wise. `res.match` has the result of `match`ing the incoming message against the regular expression. This is just a [JavaScript thing](http://www.w3schools.com/jsref/jsref_match.asp), which ends up being an array with index 0 being the full text matching the expression. If you include capture groups, those will be populated on `res.match`. For example, if we update a script like: +So far, our scripts have had static responses, which while amusing, are boring functionality-wise. `res.match` has the result of `match`ing the incoming message against the regular expression. This is just a [JavaScript thing](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match), which ends up being an array with index 0 being the full text matching the expression. If you include capture groups, those will be populated on `res.match`. For example, if we update a script like: ```javascript - robot.respond(/open the (.*) doors/i, (res) => { + robot.respond(/open the (.*) doors/i, async res => { // your code here } ``` @@ -125,19 +140,19 @@ So far, our scripts have had static responses, which while amusing, are boring f If Dave says "HAL: open the pod bay doors", then `res.match[0]` is "open the pod bay doors", and `res.match[1]` is just "pod bay". Now we can start doing more dynamic things: ```javascript - robot.respond(/open the (.*) doors/i, (res) => { + robot.respond(/open the (.*) doors/i, async res => { const doorType = res.match[1] if (doorType == 'pod bay') { - res.reply(`I'm afraid I can't let you do that.`) + await res.reply(`I'm afraid I can't let you do that.`) } else { - res.reply(`Opening ${doorType} doors`) + await res.reply(`Opening ${doorType} doors`) } } ``` ## Making HTTP calls (please use `fetch` instead) -Hubot can make HTTP calls on your behalf to integrate & consume third party APIs. This can be through an instance of [ScopedHttpClient](../src/httpclient.js) available at `robot.http`. The simplest case looks like: +Hubot can make HTTP calls on your behalf to integrate & consume third party APIs. This can be through an instance of [ScopedHttpClient](https://github.com/hubotio/hubot/blob/main/src/httpclient.js) available at `robot.http`. The simplest case looks like: ```javascript @@ -252,7 +267,7 @@ For consuming a Web Service that responds with HTML, you'll need an HTML parser. ### Advanced HTTP and HTTPS settings -As mentioned previously, Hubot uses [ScopedHttpClient](../src/httpclient.js) to provide a simple interface for making HTTP and HTTPS requests. Under the hood, it's using node's [http](http://nodejs.org/api/http.html) and [https](http://nodejs.org/api/https.html) modules, but tries to provide an easier Domain Specific Language (DSL) for common kinds of Web Service interactions. +As mentioned previously, Hubot uses [ScopedHttpClient](https://github.com/hubotio/hubot/blob/main/src/httpclient.js) to provide a simple interface for making HTTP and HTTPS requests. Under the hood, it's using node's [http](http://nodejs.org/api/http.html) and [https](http://nodejs.org/api/https.html) modules, but tries to provide an easier Domain Specific Language (DSL) for common kinds of Web Service interactions. If you need to control options on `http` and `https` more directly, you pass a second parameter to `robot.http` that will be passed on to `ScopedHttpClient` which will be passed on to `http` and `https`: @@ -279,9 +294,10 @@ res.send(res.random(lulz)) Hubot can react to a room's topic changing, assuming that the adapter supports it. ```javascript -module.exports = (robot) => { - robot.topic((res) => { - res.send()`${res.message.text}? That's a Paddlin'`) +// .mjs +export default async robot => { + robot.topic(async res => { + await res.send()`${res.message.text}? That's a Paddlin'`) }) } ``` @@ -291,16 +307,17 @@ module.exports = (robot) => { Hubot can see users entering and leaving, assuming that the adapter supports it. ```javascript +// .mjs const enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you'] const leaveReplies = ['Are you still there?', 'Target lost', 'Searching'] -module.exports = (robot) => { - robot.enter(res) => { - res.send(res.random(enterReplies)) - } - robot.leave(res) => { - res.send(res.random(leaveReplies)) - } +export default async robot => { + robot.enter(async res => { + await res.send(res.random(enterReplies)) + }) + robot.leave(async res => { + await res.send(res.random(leaveReplies)) + }) } ``` @@ -311,7 +328,8 @@ While the above helpers cover most of the functionality the average user needs ( The match function must return a truthy value if the listener callback should be executed. The truthy return value of the match function is then passed to the callback as `res.match`. ```javascript -module.exports = (robot) =>{ +// .mjs +export default async robot =>{ robot.listen( (message) => { // Match function @@ -321,28 +339,29 @@ module.exports = (robot) =>{ // Occassionally respond to things that Steve says return message.user.name == 'Steve' && Math.random() > 0.8 }, - (res) => { + async res => { // Standard listener callback // Let Steve know how happy you are that he exists - res.reply(`HI STEVE! YOU'RE MY BEST FRIEND! (but only like ${res.match * 100}% of the time)`) + await res.reply(`HI STEVE! YOU'RE MY BEST FRIEND! (but only like ${res.match * 100}% of the time)`) } ) } ``` -See [the design patterns document](patterns.md#dynamic-matching-of-messages) for examples of complex matchers. +See [the design patterns document](patterns.html#dynamic-matching-of-messages) for examples of complex matchers. ## Environment variables Hubot can access the environment he's running in, just like any other Node.js program, using [`process.env`](http://nodejs.org/api/process.html#process_process_env). This can be used to configure how scripts are run, with the convention being to use the `HUBOT_` prefix. ```javascript +// .mjs const answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING -module.exports = (robot) => { - robot.respond(/what is the answer to the ultimate question of life/, (res) => { - res.send(`${answer}, but what is the question?`) - } +export default async robot => { + robot.respond(/what is the answer to the ultimate question of life/, async res => { + await res.send(`${answer}, but what is the question?`) + }) } ``` @@ -351,49 +370,52 @@ Take care to make sure the script can load if it's not defined, give the Hubot d Here we can default to something: ```javascript +// .mjs const answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING ?? 42 -module.exports = (robot) => { - robot.respond(/what is the answer to the ultimate question of life/, (res) => { - res.send(`${answer}, but what is the question?`) - } +export default async robot => { + robot.respond(/what is the answer to the ultimate question of life/, async res => { + await res.send(`${answer}, but what is the question?`) + }) } ``` Here we exit if it's not defined: ```javascript +// .mjs const answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING if(!answer) { console.log(`Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again`) process.exit(1) } -module.exports = (robot) => { - robot.respond(/what is the answer to the ultimate question of life/, (res) => { - res.send(`${answer}, but what is the question?`) - } +export default async robot => { + robot.respond(/what is the answer to the ultimate question of life/, async res => { + await res.send(`${answer}, but what is the question?`) + }) } ``` And lastly, we update the `robot.respond` to check it: ```javascript +// .mjs const answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING -module.exports = (robot) => { - robot.respond(/what is the answer to the ultimate question of life/, (res) => { +export default async robot => { + robot.respond(/what is the answer to the ultimate question of life/, async res => { if(!answer) { - return res.send('Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again') + return await res.send('Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again') } - res.send(`${answer}, but what is the question?`) - } + await res.send(`${answer}, but what is the question?`) + }) } ``` ## Dependencies -Hubot uses [npm](https://github.com/isaacs/npm) to manage its dependencies. To add additional packages, add them to `dependencies` in `package.json`. For example, to add lolimadeupthispackage 1.2.3, it'd look like: +Hubot uses [npm](https://www.npmjs.com) to manage its dependencies. To add additional packages, add them to `dependencies` in `package.json`. For example, to add lolimadeupthispackage 1.2.3, it'd look like: ```json "dependencies": { @@ -402,6 +424,8 @@ Hubot uses [npm](https://github.com/isaacs/npm) to manage its dependencies. To a }, ``` +by executing `npm i lolimadeupthispackage@1.2.3`. + If you are using scripts from hubot-scripts, take note of the `Dependencies` documentation in the script to add. They are listed in a format that can be copy & pasted into `package.json`, just make sure to add commas as necessary to make it valid JSON. # Timeouts and Intervals @@ -409,10 +433,11 @@ If you are using scripts from hubot-scripts, take note of the `Dependencies` doc Hubot can run code later using JavaScript's built-in [setTimeout](http://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg). It takes a callback method, and the amount of time to wait before calling it: ```javascript -module.exports = (robot) => { - robot.respond(/you are a little slow/, (res) => { - setTimeout(() => { - res.send(`Who you calling 'slow'?`) +// .mjs +export default async robot => { + robot.respond(/you are a little slow/, async res => { + setTimeout(async () => { + await res.send(`Who you calling 'slow'?`) }, 60 * 1000) }) } @@ -421,11 +446,12 @@ module.exports = (robot) => { Additionally, Hubot can run code on an interval using [setInterval](http://nodejs.org/api/timers.html#timers_setinterval_callback_delay_arg). It takes a callback method, and the amount of time to wait between calls: ```javascript -module.exports = (robot) => { - robot.respond(/annoy me/, (res) => { - res.send('Hey, want to hear the most annoying sound in the world?') - setInterval(() => { - res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH') +// .mjs +export default async robot => { + robot.respond(/annoy me/, async res => { + await res.send('Hey, want to hear the most annoying sound in the world?') + setInterval(async () => { + await res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH') }, 1000) }) } @@ -434,27 +460,28 @@ module.exports = (robot) => { Both `setTimeout` and `setInterval` return the ID of the timeout or interval it created. This can be used to to `clearTimeout` and `clearInterval`. ```javascript -module.exports = (robot) => { +// .mjs +export default async robot => { let annoyIntervalId = null - robot.respond(/annoy me/, (res) => { + robot.respond(/annoy me/, async res => { if (annoyIntervalId) { - return res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH') + return await res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH') } - res.send('Hey, want to hear the most annoying sound in the world?') - annoyIntervalId = setInterval(() => { - res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH') + await res.send('Hey, want to hear the most annoying sound in the world?') + annoyIntervalId = setInterval(async () => { + await res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH') }, 1000) } - robot.respond(/unannoy me/, (res) => { + robot.respond(/unannoy me/, async res => { if (annoyIntervalId) { - res.send('GUYS, GUYS, GUYS!') + await res.send('GUYS, GUYS, GUYS!') clearInterval(annoyIntervalId) annoyIntervalId = null } else { - res.send('Not annoying you right now, am I?') + await res.send('Not annoying you right now, am I?') } } } @@ -470,14 +497,15 @@ The most common use of this is for providing HTTP end points for services with w ```javascript -module.exports = (robot) => { +// .mjs +export default async robot => { // the expected value of :room is going to vary by adapter, it might be a numeric id, name, token, or some other value - robot.router.post('/hubot/chatsecrets/:room', (req, res) => { + robot.router.post('/hubot/chatsecrets/:room', async (req, res) => { // NOTE: this is the Express route handler, not Hubot's, so `res.send` isn't async/await. const room = req.params.room const data = req.body?.payload ? JSON.parse(req.body.payload) : req.body const secret = data.secret - robot.messageRoom(room, `I have a secret: ${secret}`) + await robot.messageRoom(room, `I have a secret: ${secret}`) res.send('OK') }) @@ -503,8 +531,8 @@ Hubot can also respond to events which can be used to pass data between scripts. One use case for this would be to have one script for handling interactions with a service, and then emitting events as they come up. For example, we could have a script that receives data from a GitHub post-commit hook, make that emit commits as they come in, and then have another script act on those commits. ```javascript -// src/scripts/github-commits.js -module.exports = (robot) => { +// src/scripts/github-commits.mjs +export default async robot => { robot.router.post('/hubot/gh-commits', (req, res) => { robot.emit('commit', { user: {}, //hubot user object @@ -516,10 +544,10 @@ module.exports = (robot) => { ``` ```javascript -// src/scripts/heroku.js -module.exports = (robot) => { - robot.on('commit', (commit) => { - robot.send(commit.user, `Will now deploy ${commit.hash} from ${commit.repo}!`) +// src/scripts/heroku.mjs +export default async robot => { + robot.on('commit', async (commit) => { + await robot.send(commit.user, `Will now deploy ${commit.hash} from ${commit.repo}!`) // deploy code goes here } } @@ -527,18 +555,18 @@ module.exports = (robot) => { If you provide an event, it's highly recommended to include a hubot user or room object in its data. This would allow for hubot to notify a user or room in chat. -## Error Handling +## Error Handling No code is perfect, and errors and exceptions are to be expected. Previously, an uncaught exceptions would crash your hubot instance. Hubot now includes an `uncaughtException` handler, which provides hooks for scripts to do something about exceptions. ```javascript -// src/scripts/does-not-compute.js -module.exports = (robot) => { - robot.error((err, res) => { +// src/scripts/does-not-compute.mjs +export default async robot => { + robot.error(async (err, res) => { robot.logger.error('DOES NOT COMPUTE') if(res) { - res.reply('DOES NOT COMPUTE') + await res.reply('DOES NOT COMPUTE') } } } @@ -614,36 +642,36 @@ When documenting commands, here are some best practices: The other sections are more relevant to developers of the bot, particularly dependencies, configuration variables, and notes. All contributions to [hubot-scripts](https://github.com/github/hubot-scripts) should include all these sections that are related to getting up and running with the script. -## Persistence +## Persistence Hubot has two persistence methods available that can be used to store and retrieve data by scripts: an in-memory key-value store exposed as `robot.brain`, and an optional persistent database-backed key-value store expsoed as `robot.datastore`. ### Brain ```javascript -robot.respond(/have a soda/i, (res) => { +robot.respond(/have a soda/i, async res => { // Get number of sodas had (coerced to a number). const sodasHad = robot.brain.get('totalSodas') * 1 ?? 0 if (sodasHad > 4) { - res.reply(`I'm too fizzy..`) + await res.reply(`I'm too fizzy..`) } else { - res.reply('Sure!') + await res.reply('Sure!') robot.brain.set('totalSodas', sodasHad + 1) } }) -robot.respond(/sleep it off/i, (res) => { +robot.respond(/sleep it off/i, async res => { robot.brain.set('totalSodas', 0) - res.reply('zzzzz') + await res.reply('zzzzz') } ``` If the script needs to lookup user data, there are methods on `robot.brain` for looking up one or many users by id, name, or 'fuzzy' matching of name: `userForName`, `userForId`, `userForFuzzyName`, and `usersForFuzzyName`. ```javascript -module.exports = (robot) => { - robot.respond(/who is @?([\w .\-]+)\?*$/i, (res) => { +export default async robot => { + robot.respond(/who is @?([\w .\-]+)\?*$/i, async res => { const name = res.match[1].trim() const users = robot.brain.usersForFuzzyName(name) @@ -651,7 +679,7 @@ module.exports = (robot) => { const user = users[0] // Do something interesting here.. } - res.send(`${name} is user - ${user}`) + await res.send(`${name} is user - ${user}`) }) } ``` @@ -661,41 +689,39 @@ module.exports = (robot) => { Unlike the brain, the datastore's getter and setter methods are asynchronous and don't resolve until the call to the underlying database has resolved. This requires a slightly different approach to accessing data: ```javascript -robot.respond(/have a soda/i, (res) => { +robot.respond(/have a soda/i, async res => { // Get number of sodas had (coerced to a number). robot.datastore.get('totalSodas').then((value) => { const sodasHad = value * 1 ?? 0 if (sodasHad > 4) { - res.reply(`I'm too fizzy..`) + await res.reply(`I'm too fizzy..`) } else { - res.reply('Sure!') + await res.reply('Sure!') robot.brain.set('totalSodas', sodasHad + 1) } }) }) -robot.respond(/sleep it off/i, (res) => { - robot.datastore.set('totalSodas', 0).then(() => { - res.reply('zzzzz') - }) +robot.respond(/sleep it off/i, async res => { + await robot.datastore.set('totalSodas', 0) + await res.reply('zzzzz') }) ``` The datastore also allows setting and getting values which are scoped to individual users: ```javascript -module.exports = (robot) -> +export default async robot -> - robot.respond(/who is @?([\w .\-]+)\?*$/i, (res) => { + robot.respond(/who is @?([\w .\-]+)\?*$/i, async res => { const name = res.match[1].trim() const users = robot.brain.usersForFuzzyName(name) if (users.length == 1) { const user = users[0] - user.get('roles').then((roles) => { - res.send "#{name} is #{roles.join(', ')}" - }) + const roles = await user.get('roles') + await res.send(`${name} is ${roles.join(', ')}`) } }) ``` @@ -720,42 +746,42 @@ Once you've built some new scripts to extend the abilities of your robot friend, ## See if a script already exists -Start by [checking if an NPM package](index.md#scripts) for a script like yours already exists. If you don't see an existing package that you can contribute to, then you can easily get started using the `hubot` script [yeoman](http://yeoman.io/) generator. +Start by [checking if an NPM package](https://www.npmjs.com) for a script like yours already exists. If you don't see an existing package that you can contribute to, then you can easily get started using `npx hubot --create myhubot`. -## Creating A Script Package +## Creating A Script Package -Creating a script package for hubot is very simple. Start by installing the `hubot` [yeoman](http://yeoman.io/) generator: +Creating a script package for hubot is very simple. Start by running `npx hubot --create myhubot` to create your own instance. +`cd myhubot` and create a script. For example, if we wanted to create a hubot script called "my-awesome-script": -``` -% npm install -g yo generator-hubot +```sh +% npm hubot --create my-awesome-script +% cd my-awesome-script +% mkdir src +% touch src/AwesomeScript.mjs ``` -Once you've got the hubot generator installed, creating a hubot script is similar to creating a new hubot. You create a directory for your hubot script and generate a new `hubot:script` in it. For example, if we wanted to create a hubot script called "my-awesome-script": +Open `package.json` and add: -``` -% mkdir hubot-my-awesome-script -% cd hubot-my-awesome-script -% yo hubot:script +```json +"peerDependencies": { + "hubot": ">=9" +}, ``` -At this point, you'll be asked a few questions about the author of the script, name of the script (which is guessed by the directory name), a short description, and keywords to find it (we suggest having at least `hubot, hubot-scripts` in this list). - If you are using git, the generated directory includes a .gitignore, so you can initialize and add everything: -``` +```sh % git init % git add . % git commit -m "Initial commit" ``` -You now have a hubot script repository that's ready to roll! Feel free to crack open the pre-created `src/awesome-script.js` file and start building up your script! When you've got it ready, you can publish it to [npmjs](http://npmjs.org) by [following their documentation](https://docs.npmjs.com/getting-started/publishing-npm-packages)! +You now have a hubot script repository that's ready to roll! Feel free to crack open `src/AwesomeScript.mjs` and start building up your script! When you've got it ready, you can publish it to [npmjs](http://npmjs.org) by [following their documentation](https://docs.npmjs.com/getting-started/publishing-npm-packages)! -You'll probably want to write some unit tests for your new script. A sample test script is written to -`test/awesome-script-test.js`, which you can run with `grunt`. For more information on tests, -see the [Testing Hubot Scripts](#testing-hubot-scripts) section. +You'll probably want to write some unit tests for your new script. Review the [Hubot Repo](https://github.com/hubotio/hubot/tree/main/test for examples creating tests. -# Listener Metadata +# Listener Metadata In addition to a regular expression and callback, the `hear` and `respond` functions also accept an optional options Object which can be used to attach arbitrary metadata to the generated Listener object. This metadata allows for easy extension of your script's behavior without modifying the script package. @@ -766,12 +792,12 @@ Additional extensions may define and handle additional metadata keys. For more i Returning to an earlier example: ```javascript -module.exports = (robot) => { - robot.respond(/annoy me/, id:'annoyance.start', (res) => { +export default async robot => { + robot.respond(/annoy me/, id:'annoyance.start', async res => { // code to annoy someone }) - robot.respond(/unannoy me/, id:'annoyance.stop', (res) => { + robot.respond(/unannoy me/, id:'annoyance.stop', async res => { // code to stop annoying someone }) } @@ -797,38 +823,33 @@ Middleware is called with: - `context` - See the each middleware type's API to see what the context will expose. -- `next` - - a Function with no additional properties that should be called to continue on to the next piece of middleware/execute the Listener callback - - `next` should be called with a single, optional parameter: either the provided `done` function or a new function that eventually calls `done`. If the parameter is not given, the provided `done` will be assumed. -- `done` - - a Function with no additional properties that should be called to interrupt middleware execution and begin executing the chain of completion functions. - - `done` should be called with no parameters - -Every middleware receives the same API signature of `context`, `next`, and -`done`. Different kinds of middleware may receive different information in the + +`return true` to allow the message to continue; `return false` to stop it from continuing. + +Every middleware receives the same API signature of `context`. Different kinds of middleware may receive different information in the `context` object. For more details, see the API for each type of middleware. ### Error Handling -For synchronous middleware (never yields to the event loop), hubot will automatically catch errors and emit an `error` event, just like in standard listeners. Hubot will also automatically call the most recent `done` callback to unwind the middleware stack. Asynchronous middleware should catch its own exceptions, emit an `error` event, and call `done`. Any uncaught exceptions will interrupt all execution of middleware completion callbacks. +Asynchronous middleware should catch its own exceptions, emit an `error` event, and return `true` or `false`. Any uncaught exceptions will interrupt all execution of middleware. -# Listener Middleware +# Listener Middleware Listener middleware inserts logic between the listener matching a message and the listener executing. This allows you to create extensions that run for every matching script. Examples include centralized authorization policies, rate limiting, logging, and metrics. Middleware is implemented like other hubot scripts: instead of using the `hear` and `respond` methods, middleware is registered using `listenerMiddleware`. ## Listener Middleware Examples -A fully functioning example can be found in [hubot-rate-limit](https://github.com/michaelansel/hubot-rate-limit/blob/master/src/rate-limit.coffee). +A fully functioning example can be found in [hubot-rate-limit](https://github.com/michaelansel/hubot-rate-limit/blob/master/src/rate-limit.coffee) (Note, this is a coffee version, non-async/await). A simple example of middleware logging command executions: ```javascript -module.exports = (robot) => { - robot.listenerMiddleware((context, next, done) => { +export default async robot => { + robot.listenerMiddleware(async context => { // Log commands robot.logger.info(`${context.response.message.user.name} asked me to ${context.response.message.text}`) // Continue executing middleware - next() + return true }) } ``` @@ -838,11 +859,11 @@ In this example, a log message will be written for each chat message that matche A more complex example making a rate limiting decision: ```javascript -module.exports = (robot) => { +export default async robot => { // Map of listener ID to last time it was executed let lastExecutedTime = {} - robot.listenerMiddleware((context, next, done) => { + robot.listenerMiddleware(async context => { try { // Default to 1s unless listener provides a different minimum period const minPeriodMs = context.listener.options?.rateLimits?.minPeriodMs ?? 1000 @@ -851,12 +872,10 @@ module.exports = (robot) => { if (lastExecutedTime.hasOwnProperty(context.listener.options.id) && lastExecutedTime[context.listener.options.id] > Date.now() - minPeriodMs) { // Command is being executed too quickly! - done() + return false } else { - next(()=> { - lastExecutedTime[context.listener.options.id] = Date.now() - done() - }) + lastExecutedTime[context.listener.options.id] = Date.now() + return true } } catch(err) { robot.emit('error', err, context.response) @@ -865,24 +884,23 @@ module.exports = (robot) => { } ``` -In this example, the middleware checks to see if the listener has been executed in the last 1,000ms. If it has, the middleware calls `done` immediately, preventing the listener callback from being called. If the listener is allowed to execute, the middleware attaches a `done` handler so that it can record the time the listener *finished* executing. +In this example, the middleware checks to see if the listener has been executed in the last 1,000ms. If it has, the middleware `return false` immediately, preventing the listener callback from being called. If the listener is allowed to execute, the middleware records the time the listener *finished* executing and `return true`. This example also shows how listener-specific metadata can be leveraged to create very powerful extensions: a script developer can use the rate limiting middleware to easily rate limit commands at different rates by just adding the middleware and setting a listener option. ```javascript -module.exports = (robot) => { - robot.hear(/hello/, id: 'my-hello', rateLimits: {minPeriodMs: 10000}, (res) => { +// .mjs +export default async robot => { + robot.hear(/hello/, id: 'my-hello', rateLimits: {minPeriodMs: 10000}, async res => { // This will execute no faster than once every ten seconds - res.reply('Why, hello there!') + await res.reply('Why, hello there!') }) } ``` ## Listener Middleware API -Listener middleware callbacks receive three parameters, `context`, `next`, and -`done`. See the [middleware API](#execution-process-and-api) for a description -of `next` and `done`. Listener middleware context includes these fields: +Listener middleware callbacks receive 1 argument, `context`. Listener middleware context includes these fields: - `listener` - `options`: a simple Object containing options set when defining the listener. See [Listener Metadata](#listener-metadata). - all other properties should be considered internal @@ -898,9 +916,7 @@ excluded commands that have not been updated to add an ID, metrics, and more. ## Receive Middleware Example -This simple middlware bans hubot use by a particular user, including `hear` -listeners. If the user attempts to run a command explicitly, it will return -an error message. +This simple middlware bans hubot use by a particular user, including `hear` listeners. If the user attempts to run a command explicitly, it will return an error message. ```javascript const EXCLUDED_USERS = [ @@ -915,7 +931,7 @@ robot.receiveMiddleware(async context => { // If the message starts with 'hubot' or the alias pattern, this user was // explicitly trying to run a command, so respond with an error message. if (context.response.message.text?.match(robot.respondPattern(''))) { - context.response.reply(`I'm sorry @${context.response.message.user.name}, but I'm configured to ignore your commands.`) + await context.response.reply(`I'm sorry @${context.response.message.user.name}, but I'm configured to ignore your commands.`) } // Don't process further middleware. @@ -928,9 +944,7 @@ robot.receiveMiddleware(async context => { ## Receive Middleware API -Receive middleware callbacks receive three parameters, `context`, `next`, and -`done`. See the [middleware API](#execution-process-and-api) for a description -of `next` and `done`. Receive middleware context includes these fields: +Receive middleware callbacks receive 1 argument, `context`. Receive middleware context includes these fields: - `response` - this response object will not have a `match` property, as no listeners have been run yet to match it. - middleware may decorate the response object with additional information (e.g. add a property to `response.message.user` with a user's LDAP groups) @@ -938,32 +952,28 @@ of `next` and `done`. Receive middleware context includes these fields: # Response Middleware -Response middleware runs against every message hubot sends to a chat room. It's -helpful for message formatting, preventing password leaks, metrics, and more. +Response middleware runs against every message hubot sends to a chat room. It's helpful for message formatting, preventing password leaks, metrics, and more. ## Response Middleware Example -This simple example changes the format of links sent to a chat room from -markdown links (like [example](https://example.com)) to the format supported -by [Slack](https://slack.com), . +This simple example changes the format of links sent to a chat room from markdown links (like [example](https://example.com)) to the format supported by [Slack](https://slack.com), . ```javascript -module.exports = (robot)=> { - robot.responseMiddleware((context, next, done) => { - if(!context.plaintext) return +// .mjs +export default async robot=> { + robot.responseMiddleware(async context=> { + if(!context.plaintext) return true context.strings.forEach(string => { string.replace(/\[([^\[\]]*?)\]\((https?:\/\/.*?)\)/, "<$2|$1>" }) - next() + return true }) } ``` ## Response Middleware API -Response middleware callbacks receive three parameters, `context`, `next`, and -`done`. See the [middleware API](#execution-process-and-api) for a description -of `next` and `done`. Receive middleware context includes these fields: +Response middleware callbacks receive 1 parameters, `context` and are Promises/async/await. Receive middleware context includes these fields: - `response` - This response object can be used to send new messages from the middleware. Middleware will be called on these new responses. Be careful not to create infinite loops. - `strings` @@ -975,30 +985,30 @@ of `next` and `done`. Receive middleware context includes these fields: # Testing Hubot Scripts -[hubot-test-helper](https://github.com/mtsmfm/hubot-test-helper) is a good -framework for unit testing Hubot scripts. (Note that, in order to use -hubot-test-helper, you'll need a recent Node.js version with support for Promises.) +[hubot-test-helper](https://github.com/mtsmfm/hubot-test-helper) is a good framework for unit testing Hubot scripts. (Note that, in order to use hubot-test-helper, you'll need a recent Node.js version with support for Promises.) Install the package in your Hubot instance: -``` % npm install hubot-test-helper --save-dev ``` +`% npm install hubot-test-helper --save-dev` You'll also need to install: - * a JavaScript testing framework such as *Mocha* - * an assertion library such as *chai* or *expect.js* +* a JavaScript testing framework such as *Mocha* +* an assertion library such as *chai* or *expect.js* +* Or just use [Node's Test Runner](https://nodejs.org/dist/latest-v20.x/docs/api/test.html) You may also want to install: - * a mocking library such as *Sinon.js* (if your script performs webservice calls or - other asynchronous actions) +* a mocking library such as *Sinon.js* (if your script performs webservice calls or + other asynchronous actions) +* Or just use Node Test Runner's Mocking facility [Note: This section is still refering to Coffeescript, but we've update Hubot for Javascript. We'll have to replace this when we get a JavaScript example.] -Here is a sample script that tests the first couple of commands in the -[Hubot sample script](https://github.com/hubotio/generator-hubot/blob/master/generators/app/templates/scripts/example.coffee). This script uses *Mocha*, *chai*, *coffeescript*, and of course *hubot-test-helper*: +Here is a sample script that tests the first couple of commands in the [Hubot sample script](https://github.com/hubotio/generator-hubot/blob/master/generators/app/templates/scripts/example.coffee). This script uses *Mocha*, *chai*, *coffeescript*, and of course *hubot-test-helper*: **test/example-test.coffee** + ```coffeescript Helper = require('hubot-test-helper') chai = require 'chai' @@ -1037,15 +1047,12 @@ describe 'example script', -> ``` **sample output** -```bash -% mocha --require coffeescript/register test/*.coffee - +```sh +% mocha --require coffeescript/register test/*.coffee example script ✓ doesn't need badgers ✓ won't open the pod bay doors ✓ will open the dutch doors - - 3 passing (212ms) ``` diff --git a/package.json b/package.json index 6f9ad0a7b..53f0f9c18 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,9 @@ "pretest": "standard", "test": "node --test", "test:smoke": "node src/**/*.js", - "test:e2e": "bin/e2e-test.sh" + "test:e2e": "bin/e2e-test.sh", + "build:local": "npx @hubot-friends/sfab --folder ./docs --destination ./_site --verbose --serve --watch-path ./docs", + "build": "npx @hubot-friends/sfab --folder ./docs --destination ./_site --verbose" }, "release": { "branches": [