├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .sonarcloud.properties ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── action └── Dockerfile ├── bin ├── build_examples.sh └── test_examples.sh ├── docs ├── README.md └── publish │ └── README.md ├── examples ├── angular │ ├── .editorconfig │ ├── .gimbalrc.yml │ ├── .gitignore │ ├── README.md │ ├── angular.json │ ├── browserslist │ ├── e2e │ │ ├── protractor.conf.js │ │ ├── src │ │ │ ├── app.e2e-spec.ts │ │ │ └── app.po.ts │ │ └── tsconfig.json │ ├── karma.conf.js │ ├── package.json │ ├── src │ │ ├── app │ │ │ ├── app-routing.module.ts │ │ │ ├── app.component.css │ │ │ ├── app.component.html │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ └── app.module.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.css │ │ └── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ └── yarn.lock └── react │ ├── .gimbalrc.yml │ ├── .gitignore │ ├── README.md │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ └── serviceWorker.js │ └── yarn.lock ├── lerna.json ├── package.json ├── packages ├── gimbal-core │ ├── .eslintignore │ ├── .eslintrc.js │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── Table │ │ │ │ ├── index.ts │ │ │ │ └── renderer │ │ │ │ │ ├── cli.ts │ │ │ │ │ ├── html.ts │ │ │ │ │ └── markdown.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── logger │ │ │ ├── ci.ts │ │ │ ├── cli.ts │ │ │ └── index.ts │ │ └── utils │ │ │ ├── Process.ts │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.ts │ │ │ ├── colors.spec.ts │ │ │ ├── colors.ts │ │ │ ├── env.spec.ts │ │ │ ├── env.ts │ │ │ ├── fs.ts │ │ │ ├── port.ts │ │ │ ├── spawn.ts │ │ │ ├── string.spec.ts │ │ │ ├── string.ts │ │ │ ├── threshold.spec.ts │ │ │ └── threshold.ts │ └── tsconfig.json ├── gimbal │ ├── .dockerignore │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── Dockerfile │ ├── LICENSE │ ├── README.md │ ├── bin │ │ └── index.js │ ├── docker │ │ ├── build.sh │ │ └── run.sh │ ├── docs │ │ ├── README.md │ │ ├── ci │ │ │ ├── README.md │ │ │ ├── bitbucket │ │ │ │ ├── README.md │ │ │ │ ├── bitbucket-pipelines.yml │ │ │ │ ├── bitbucket-pipelines_complete.yml │ │ │ │ └── bitbucket-pipelines_gimbal-only.yml │ │ │ ├── circleci │ │ │ │ ├── README.md │ │ │ │ └── sample.yml │ │ │ ├── github │ │ │ │ ├── README.md │ │ │ │ └── workflow.yml │ │ │ └── travisci │ │ │ │ ├── README.md │ │ │ │ └── sample.yml │ │ ├── command │ │ │ ├── README.md │ │ │ ├── audit │ │ │ │ └── README.md │ │ │ ├── heap-snapshot │ │ │ │ └── README.md │ │ │ ├── lighthouse │ │ │ │ └── README.md │ │ │ ├── size │ │ │ │ └── README.md │ │ │ └── unused-source │ │ │ │ └── README.md │ │ ├── config │ │ │ └── README.md │ │ ├── configurations.md │ │ ├── event │ │ │ └── README.md │ │ ├── module │ │ │ ├── README.md │ │ │ ├── chrome │ │ │ │ └── README.md │ │ │ ├── heap-snapshot │ │ │ │ └── README.md │ │ │ ├── lighthouse │ │ │ │ └── README.md │ │ │ ├── serve │ │ │ │ └── README.md │ │ │ ├── size │ │ │ │ └── README.md │ │ │ └── unused-source │ │ │ │ └── README.md │ │ ├── output │ │ │ ├── README.md │ │ │ ├── html │ │ │ │ └── README.md │ │ │ ├── json │ │ │ │ └── README.md │ │ │ └── markdown │ │ │ │ └── README.md │ │ ├── utils │ │ │ └── README.md │ │ └── vcs │ │ │ ├── README.md │ │ │ └── github │ │ │ └── README.md │ ├── envs │ │ ├── CircleCI │ │ │ ├── commit.env │ │ │ └── pr.env │ │ ├── CodeBuild │ │ │ └── commit.env │ │ └── TravisCI │ │ │ ├── commit.env │ │ │ └── pr.env │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── README.md │ │ ├── ascii_art │ │ │ └── gimbal.txt │ │ ├── ci │ │ │ ├── CircleCI │ │ │ │ └── index.ts │ │ │ ├── GitHubActions │ │ │ │ └── index.ts │ │ │ ├── TravisCI │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── command │ │ │ ├── audit │ │ │ │ ├── index.ts │ │ │ │ └── program.ts │ │ │ ├── index.ts │ │ │ └── reconcile.ts │ │ ├── config │ │ │ ├── audits.ts │ │ │ ├── index.ts │ │ │ ├── jobs.ts │ │ │ ├── loader │ │ │ │ ├── js.ts │ │ │ │ └── yaml.ts │ │ │ ├── plugin │ │ │ │ └── index.ts │ │ │ └── resolver │ │ │ │ └── index.ts │ │ ├── event │ │ │ ├── Event.spec.ts │ │ │ ├── Event.ts │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── logger │ │ │ └── index.ts │ │ ├── module │ │ │ ├── chrome │ │ │ │ ├── default-config.ts │ │ │ │ └── index.ts │ │ │ ├── heap-snapshot │ │ │ │ ├── default-config.ts │ │ │ │ ├── index.ts │ │ │ │ ├── meta.ts │ │ │ │ ├── output.ts │ │ │ │ ├── reconcile.ts │ │ │ │ └── register.ts │ │ │ ├── lighthouse │ │ │ │ ├── default-config.ts │ │ │ │ ├── index.ts │ │ │ │ ├── meta.ts │ │ │ │ ├── output.ts │ │ │ │ ├── reconcile.ts │ │ │ │ └── register.ts │ │ │ ├── reconcile.ts │ │ │ ├── registry.ts │ │ │ ├── serve │ │ │ │ └── index.ts │ │ │ ├── size │ │ │ │ ├── default-config.ts │ │ │ │ ├── index.ts │ │ │ │ ├── meta.ts │ │ │ │ ├── output.ts │ │ │ │ └── register.ts │ │ │ └── unused-source │ │ │ │ ├── default-config.ts │ │ │ │ ├── index.ts │ │ │ │ ├── meta.ts │ │ │ │ ├── output.ts │ │ │ │ ├── reconcile.ts │ │ │ │ └── register.ts │ │ ├── modules.ts │ │ ├── output │ │ │ ├── cli │ │ │ │ └── index.ts │ │ │ ├── filter.ts │ │ │ ├── html │ │ │ │ ├── index.ts │ │ │ │ └── template.html │ │ │ ├── index.ts │ │ │ ├── json │ │ │ │ └── index.ts │ │ │ └── markdown │ │ │ │ └── index.ts │ │ ├── utils │ │ │ ├── command.ts │ │ │ └── constants.ts │ │ └── vcs │ │ │ ├── GitHub │ │ │ └── index.ts │ │ │ ├── comment │ │ │ └── index.ts │ │ │ └── index.ts │ ├── tsconfig.eslint.json │ └── tsconfig.json ├── plugin-axe │ ├── .eslintignore │ ├── .eslintrc.js │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.spec.ts │ │ └── index.ts │ └── tsconfig.json ├── plugin-last-value │ ├── .eslintignore │ ├── .eslintrc.js │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.spec.ts │ │ ├── index.ts │ │ ├── render.spec.ts │ │ ├── render.ts │ │ ├── storage.spec.ts │ │ ├── storage.ts │ │ ├── util.spec.ts │ │ └── util.ts │ └── tsconfig.json ├── plugin-mysql │ ├── .eslintignore │ ├── .eslintrc.js │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.spec.ts │ │ ├── index.ts │ │ └── last-value │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ └── tsconfig.json ├── plugin-source-map-explorer │ ├── .eslintignore │ ├── .eslintrc.js │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── config.spec.ts │ │ ├── config.ts │ │ ├── index.spec.ts │ │ ├── index.ts │ │ ├── mod.spec.ts │ │ └── mod.ts │ └── tsconfig.json ├── plugin-sqlite │ ├── .eslintignore │ ├── .eslintrc.js │ ├── README.md │ ├── jest.config.js │ ├── package.json │ ├── src │ │ ├── index.spec.ts │ │ ├── index.ts │ │ └── last-value │ │ │ ├── index.spec.ts │ │ │ └── index.ts │ └── tsconfig.json └── typings │ ├── .eslintrc.js │ ├── ci │ └── index.d.ts │ ├── command │ └── index.d.ts │ ├── components │ └── Table │ │ └── index.d.ts │ ├── config │ ├── index.d.ts │ ├── jobs.d.ts │ └── plugin │ │ └── index.d.ts │ ├── event │ ├── Event.d.ts │ └── index.d.ts │ ├── logger │ └── index.d.ts │ ├── module │ ├── chrome │ │ └── index.d.ts │ ├── heap-snapshot │ │ └── index.d.ts │ ├── index.d.ts │ ├── lighthouse │ │ └── index.d.ts │ ├── registry.d.ts │ ├── serve │ │ └── index.d.ts │ ├── size │ │ └── index.d.ts │ └── unused-source │ │ └── index.d.ts │ ├── output │ ├── cli.d.ts │ ├── index.d.ts │ └── markdown │ │ └── index.d.ts │ ├── package.json │ ├── plugin │ └── last-value │ │ ├── index.d.ts │ │ └── util.d.ts │ ├── tsconfig.json │ ├── utils │ ├── Queue.d.ts │ ├── command.d.ts │ ├── env.d.ts │ ├── fs.d.ts │ ├── spawn.d.ts │ └── threshold.d.ts │ └── vcs │ ├── GitHub │ └── index.d.ts │ ├── comment │ └── index.d.ts │ └── index.d.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | package.json 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | env: { 5 | es6: true, 6 | jest: true, 7 | node: true, 8 | 'jest/globals': true 9 | }, 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:@typescript-eslint/recommended', 13 | 'airbnb', 14 | 'plugin:jest/recommended', 15 | 'plugin:prettier/recommended', 16 | 'prettier/@typescript-eslint', 17 | 'plugin:import/errors', 18 | 'plugin:import/warnings', 19 | 'plugin:import/typescript' 20 | ], 21 | parser: '@typescript-eslint/parser', 22 | plugins: ['@typescript-eslint', 'prettier', 'import', 'jest'], 23 | rules: { 24 | 'prettier/prettier': 'error', 25 | 'class-methods-use-this': 'off', 26 | curly: ['error', 'all'], 27 | 'max-len': [ 28 | 'error', 29 | { 30 | code: 120, 31 | ignoreComments: true, 32 | ignoreRegExpLiterals: true, 33 | ignoreStrings: true, 34 | ignoreTemplateLiterals: true, 35 | ignoreTrailingComments: true, 36 | ignoreUrls: true 37 | } 38 | ], 39 | 'no-console': 'error', 40 | '@typescript-eslint/indent': ['error', 2], 41 | '@typescript-eslint/no-unused-vars': 'error', 42 | '@typescript-eslint/no-explicit-any': 'error', 43 | // favor prettier here 44 | '@typescript-eslint/indent': 'off', 45 | // sigh for now 46 | '@typescript-eslint/ban-ts-ignore': 'off' 47 | }, 48 | settings: { 49 | 'import/resolver': { 50 | typescript: {}, 51 | 'eslint-import-resolver-lerna': { 52 | packages: path.resolve(__dirname, 'packages') 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Project 2 | 3 | ### Code of Conduct 4 | 5 | Modus has adopted a [Code of Conduct](../CODE_OF_CONDUCT.md) that we expect project participants to adhere to. 6 | 7 | ### Submitting a Pull Request 8 | 9 | If you are a first time contributor, you can learn how from this _free_ series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) 10 | 11 | ### License 12 | 13 | By contributing, you agree that your contributions will belicensed under it's [license](../LICENSE) 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | **Expected behavior** 22 | A clear and concise description of what you expected to happen. 23 | 24 | **Screenshots** 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | **Desktop (please complete the following information):** 28 | 29 | - OS: [e.g. iOS] 30 | - Browser [e.g. chrome, safari] 31 | - Version [e.g. 22] 32 | 33 | **Smartphone (please complete the following information):** 34 | 35 | - Device: [e.g. iPhone6] 36 | - OS: [e.g. iOS8.1] 37 | - Browser [e.g. stock browser, safari] 38 | - Version [e.g. 22] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Pull request checklist 4 | 5 | Please check if your PR fulfills the following requirements: 6 | 7 | - [ ] Tests for the changes have been added (for bug fixes / features) 8 | - [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features) 9 | - [ ] Build (`npm run build:check` or `yarn build:check`) was run locally and any changes were pushed 10 | - [ ] Lint (`npm run lint` or `yarn lint`) has passed locally and any fixes were made for failures 11 | 12 | ## Pull request type 13 | 14 | 15 | 16 | 17 | 18 | Please check the type of change your PR introduces: 19 | 20 | - [ ] Bugfix 21 | - [ ] Feature 22 | - [ ] Code style update (formatting, renaming) 23 | - [ ] Refactoring (no functional changes, no api changes) 24 | - [ ] Build related changes 25 | - [ ] Documentation content changes 26 | - [ ] Other (please describe): 27 | 28 | ## What is the current behavior? 29 | 30 | 31 | 32 | Issue Number: N/A 33 | 34 | ## What is the new behavior? 35 | 36 | 37 | 38 | - 39 | - 40 | - 41 | 42 | ## Does this introduce a breaking change? 43 | 44 | - [ ] Yes 45 | - [ ] No 46 | 47 | 48 | 49 | ## Other information 50 | 51 | 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | lerna-debug.log 4 | npm-debug.log 5 | packages/*/lib 6 | *.db 7 | *error.log 8 | package-lock.json 9 | artifacts/ 10 | .idea 11 | *.iml 12 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .vscode/* 2 | .gimbalrc.* 3 | package.json 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "trailingComma": "all" 5 | } 6 | -------------------------------------------------------------------------------- /.sonarcloud.properties: -------------------------------------------------------------------------------- 1 | # Path to sources 2 | #sonar.sources=. 3 | #sonar.exclusions= 4 | #sonar.inclusions= 5 | 6 | # Path to tests 7 | #sonar.tests= 8 | #sonar.test.exclusions= 9 | #sonar.test.inclusions= 10 | 11 | # Source encoding 12 | #sonar.sourceEncoding=UTF-8 13 | 14 | # Exclusions for copy-paste detection 15 | #sonar.cpd.exclusions= 16 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "dbaeumer.vscode-eslint", 8 | "editorconfig.editorconfig", 9 | "esbenp.prettier-vscode", 10 | "mtxr.sqltools" 11 | ], 12 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 13 | "unwantedRecommendations": [] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[json]": { 4 | "editor.formatOnSave": false, 5 | }, 6 | "eslint.validate": [ 7 | "javascript", 8 | "javascriptreact", 9 | "typescript", 10 | "typescriptreact" 11 | ], 12 | "eslint.workingDirectories": [{ 13 | "directory": "./packages/gimbal", 14 | "changeProcessCWD": true 15 | }, { 16 | "directory": "./packages/gimbal-core", 17 | "changeProcessCWD": true 18 | }, { 19 | "directory": "./packages/plugin-axe", 20 | "changeProcessCWD": true 21 | }, { 22 | "directory": "./packages/plugin-last-value", 23 | "changeProcessCWD": true 24 | }, { 25 | "directory": "./packages/plugin-mysql", 26 | "changeProcessCWD": true 27 | }, { 28 | "directory": "./packages/plugin-sqlite", 29 | "changeProcessCWD": true 30 | }, { 31 | "directory": "./packages/typings", 32 | "changeProcessCWD": true 33 | }], 34 | "cSpell.words": [ 35 | "brotli", 36 | "deepmerge", 37 | "directorysize", 38 | "globby", 39 | "lerna", 40 | "monorepo", 41 | "precache", 42 | "promisify" 43 | ], 44 | "sqltools.connections": [{ 45 | "database": "./examples/react/gimbal.db", 46 | "dialect": "SQLite", 47 | "name": "gimbal-example-react" 48 | }] 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-present, Modus Create, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /action/Dockerfile: -------------------------------------------------------------------------------- 1 | # Docker image 2 | FROM moduscreate/gimbal:latest 3 | 4 | # Action name 5 | LABEL "name"="Gimbal Web Performance Budgeting Action" 6 | 7 | # Repository 8 | LABEL "repository"="https://github.com/ModusCreateOrg/gimbal" 9 | 10 | # Homepage 11 | LABEL "homepage"="https://github.com/ModusCreateOrg/gimbal" 12 | 13 | # Gimbal Maintainer 14 | LABEL "maintainer"="Mitchell Simoens " 15 | 16 | # Current version 17 | LABEL "version"="1.2.3" 18 | 19 | # Action name 20 | LABEL "com.github.actions.name"="Gimbal Web Performance Budgeting Action" 21 | 22 | # Desc 23 | LABEL "com.github.actions.description"="Prevents web performance regression and outputs comments with detailed reports." 24 | 25 | # Icon 26 | LABEL "com.github.actions.icon"="package" 27 | 28 | #Color 29 | LABEL "com.github.actions.color"="blue" 30 | 31 | # Env var 32 | ENV GITHUB_ACTIONS true 33 | 34 | # Entrypoint 35 | ENTRYPOINT ["gimbal"] 36 | -------------------------------------------------------------------------------- /bin/build_examples.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 4 | set -euo pipefail 5 | IFS=$'\n\t' 6 | 7 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 8 | ${DEBUG:-false} && set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | # Credit to http://stackoverflow.com/a/246128/424301 14 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 15 | BASE_DIR="$( cd "${DIR}/.." && pwd )" 16 | EXAMPLES_DIR="$BASE_DIR/examples" 17 | 18 | for file in $EXAMPLES_DIR/*; do 19 | cd $file 20 | 21 | if [[ ! -d node_modules ]]; then 22 | echo 23 | echo "# Installing $file example" 24 | echo 25 | 26 | yarn 27 | fi 28 | 29 | # if either of these exists, continue 30 | if [[ -d build ]] || [[ -d dist ]]; then 31 | continue 32 | fi 33 | 34 | echo 35 | echo "# Building $file example" 36 | echo 37 | 38 | yarn build 39 | done 40 | -------------------------------------------------------------------------------- /bin/test_examples.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 4 | set -euo pipefail 5 | IFS=$'\n\t' 6 | 7 | # Set DEBUG to true for enhanced debugging: run prefixed with "DEBUG=true" 8 | ${DEBUG:-false} && set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | # Credit to http://stackoverflow.com/a/246128/424301 14 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 15 | BASE_DIR="$( cd "${DIR}/.." && pwd )" 16 | EXAMPLES_DIR="$BASE_DIR/examples" 17 | 18 | for file in $EXAMPLES_DIR/*; do 19 | cd $file 20 | 21 | yarn gimbal 22 | done 23 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Documentation 2 | 3 | This repo is split up into packages using [Lerna](https://lerna.js.org/) to orchestrate and manage the [monorepo](https://en.wikipedia.org/wiki/Monorepo). 4 | 5 | The following are links to the projects and their doc site: 6 | 7 | - [Gimbal](../packages/gimbal) [ [docs](../packages/gimbal/docs) ] 8 | 9 | - [ ] ci 10 | - [ ] circleci 11 | - [ ] travisci 12 | - [ ] command 13 | - [ ] audit 14 | - [ ] heap-snapshot 15 | - [ ] lighthouse 16 | - [ ] size 17 | - [ ] unused-source 18 | - [ ] config 19 | - [ ] event 20 | - [ ] module 21 | - [ ] chrome 22 | - [ ] heap-snapshot 23 | - [ ] lighthouse 24 | - [ ] serve 25 | - [ ] size 26 | - [ ] unused-source 27 | - [ ] output 28 | - [ ] html 29 | - [ ] json 30 | - [ ] markdown 31 | - [ ] utils 32 | - [ ] vcs 33 | - [ ] github 34 | 35 | ## Publishing 36 | 37 | More about publishing in this monorepo using Lerna can be seen in our [publishing](./publish) doc. 38 | -------------------------------------------------------------------------------- /docs/publish/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Publishing 2 | 3 | Gimbal is a node module that is published to the [npmjs](https://www.npmjs.com/) repository. In order to publish, the [`lerna version`](https://github.com/lerna/lerna/tree/master/commands/version) command is used to bump the version. Once the git ag is pushed, CircleCI will automatically trigger publishing to the npm repository: 4 | 5 | ```shell 6 | $ yarn run lerna:version 7 | ``` 8 | 9 | This will execute: 10 | 11 | ```shell 12 | $ lerna version patch --exact --yes 13 | ``` 14 | 15 | This will bump the patch version up so if the current version is `1.1.1`, the new version will be `1.1.2`. Please see the [`lerna version`](https://github.com/lerna/lerna/tree/master/commands/version) docs to see how to bump the version to a different version such as a new major or a specific version. 16 | -------------------------------------------------------------------------------- /examples/angular/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /examples/angular/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | .history/* 33 | 34 | # misc 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /examples/angular/README.md: -------------------------------------------------------------------------------- 1 | # ExampleAngular 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.0.0. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /examples/angular/browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /examples/angular/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | 'browserName': 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /examples/angular/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('Welcome to example-angular!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /examples/angular/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css('app-root h1')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/angular/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/angular/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/example-angular'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /examples/angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-angular", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "gimbal": "TS_NODE_PROJECT=../../packages/gimbal/tsconfig.json yarn start --cwd ../../packages/gimbal -- --cwd ../../examples/angular", 9 | "gimbal:built": "gimbal", 10 | "test": "ng test", 11 | "lint": "ng lint", 12 | "e2e": "ng e2e" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@angular/animations": "~8.0.0", 17 | "@angular/common": "~8.0.0", 18 | "@angular/compiler": "~8.0.0", 19 | "@angular/core": "~8.0.0", 20 | "@angular/forms": "~8.0.0", 21 | "@angular/platform-browser": "~8.0.0", 22 | "@angular/platform-browser-dynamic": "~8.0.0", 23 | "@angular/router": "~8.0.0", 24 | "rxjs": "~6.4.0", 25 | "tslib": "^1.9.0", 26 | "zone.js": "~0.9.1" 27 | }, 28 | "devDependencies": { 29 | "@angular-devkit/build-angular": "~0.800.0", 30 | "@angular/cli": "~8.0.0", 31 | "@angular/compiler-cli": "~8.0.0", 32 | "@angular/language-service": "~8.0.0", 33 | "@types/node": "~8.9.4", 34 | "@types/jasmine": "~3.3.8", 35 | "@types/jasminewd2": "~2.0.3", 36 | "codelyzer": "^5.0.0", 37 | "jasmine-core": "~3.4.0", 38 | "jasmine-spec-reporter": "~4.2.1", 39 | "karma": "~4.1.0", 40 | "karma-chrome-launcher": "~2.2.0", 41 | "karma-coverage-istanbul-reporter": "~2.0.1", 42 | "karma-jasmine": "~2.0.1", 43 | "karma-jasmine-html-reporter": "^1.4.0", 44 | "protractor": "~5.4.0", 45 | "ts-node": "~7.0.0", 46 | "tslint": "~5.15.0", 47 | "typescript": "~3.4.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/angular/src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | const routes: Routes = []; 5 | 6 | @NgModule({ 7 | imports: [RouterModule.forRoot(routes)], 8 | exports: [RouterModule] 9 | }) 10 | export class AppRoutingModule { } 11 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | /* making this not empty so SonarCloud doesn't yell */ 2 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | Welcome to {{ title }}! 5 |

6 | Angular Logo 7 |
8 |

Here are some links to help you start:

9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | imports: [ 9 | RouterTestingModule 10 | ], 11 | declarations: [ 12 | AppComponent 13 | ], 14 | }).compileComponents(); 15 | })); 16 | 17 | it('should create the app', () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.debugElement.componentInstance; 20 | expect(app).toBeTruthy(); 21 | }); 22 | 23 | it(`should have as title 'example-angular'`, () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | const app = fixture.debugElement.componentInstance; 26 | expect(app.title).toEqual('example-angular'); 27 | }); 28 | 29 | it('should render title in a h1 tag', () => { 30 | const fixture = TestBed.createComponent(AppComponent); 31 | fixture.detectChanges(); 32 | const compiled = fixture.debugElement.nativeElement; 33 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to example-angular!'); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'example-angular'; 10 | } 11 | -------------------------------------------------------------------------------- /examples/angular/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | AppComponent 10 | ], 11 | imports: [ 12 | BrowserModule, 13 | AppRoutingModule 14 | ], 15 | providers: [], 16 | bootstrap: [AppComponent] 17 | }) 18 | export class AppModule { } 19 | -------------------------------------------------------------------------------- /examples/angular/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/gimbal/7cbf580dd113802d205cc4341d7bea112237a39c/examples/angular/src/assets/.gitkeep -------------------------------------------------------------------------------- /examples/angular/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /examples/angular/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /examples/angular/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/gimbal/7cbf580dd113802d205cc4341d7bea112237a39c/examples/angular/src/favicon.ico -------------------------------------------------------------------------------- /examples/angular/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ExampleAngular 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/angular/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /examples/angular/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /examples/angular/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /examples/angular/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "include": [ 8 | "src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "src/test.ts", 12 | "src/**/*.spec.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /examples/angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/angular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /examples/angular/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": false, 5 | "arrow-parens": false, 6 | "deprecation": { 7 | "severity": "warn" 8 | }, 9 | "component-class-suffix": true, 10 | "contextual-lifecycle": true, 11 | "directive-class-suffix": true, 12 | "directive-selector": [ 13 | true, 14 | "attribute", 15 | "app", 16 | "camelCase" 17 | ], 18 | "component-selector": [ 19 | true, 20 | "element", 21 | "app", 22 | "kebab-case" 23 | ], 24 | "import-blacklist": [ 25 | true, 26 | "rxjs/Rx" 27 | ], 28 | "interface-name": false, 29 | "max-classes-per-file": false, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-consecutive-blank-lines": false, 47 | "no-console": [ 48 | true, 49 | "debug", 50 | "info", 51 | "time", 52 | "timeEnd", 53 | "trace" 54 | ], 55 | "no-empty": false, 56 | "no-inferrable-types": [ 57 | true, 58 | "ignore-params" 59 | ], 60 | "no-non-null-assertion": true, 61 | "no-redundant-jsdoc": true, 62 | "no-switch-case-fall-through": true, 63 | "no-use-before-declare": true, 64 | "no-var-requires": false, 65 | "object-literal-key-quotes": [ 66 | true, 67 | "as-needed" 68 | ], 69 | "object-literal-sort-keys": false, 70 | "ordered-imports": false, 71 | "quotemark": [ 72 | true, 73 | "single" 74 | ], 75 | "trailing-comma": false, 76 | "no-conflicting-lifecycle": true, 77 | "no-host-metadata-property": true, 78 | "no-input-rename": true, 79 | "no-inputs-metadata-property": true, 80 | "no-output-native": true, 81 | "no-output-on-prefix": true, 82 | "no-output-rename": true, 83 | "no-outputs-metadata-property": true, 84 | "template-banana-in-box": true, 85 | "template-no-negated-async": true, 86 | "use-lifecycle-interface": true, 87 | "use-pipe-transform-interface": true 88 | }, 89 | "rulesDirectory": [ 90 | "codelyzer" 91 | ] 92 | } -------------------------------------------------------------------------------- /examples/react/.gimbalrc.yml: -------------------------------------------------------------------------------- 1 | audits: 2 | - axe 3 | - size 4 | - lighthouse 5 | - unused-source 6 | - heap-snapshot 7 | - source-map-explorer 8 | 9 | configs: 10 | comment: 11 | onlyFailures: true 12 | 13 | outputs: 14 | html: artifacts/gimbal.html 15 | json: artifacts/gimbal.json 16 | markdown: artifacts/gimbal.md 17 | 18 | plugins: 19 | - plugin: '@modus/gimbal-plugin-axe' 20 | thresholds: 21 | bypass: serious 22 | landmark-one-main: moderate 23 | page-has-heading-one: moderate 24 | - plugin: '@modus/gimbal-plugin-source-map-explorer' 25 | bundles: 26 | - path: '**/main.*.js' 27 | thresholds: 28 | App.js: 450 B 29 | index.js: 100 B 30 | logo.svg: 250 B 31 | serviceWorker.js: 300 B 32 | : 150 B 33 | - path: '**/2.*.js' 34 | thresholds: 35 | react/index.js: 50 B 36 | object-assign/index.js: 1 KB 37 | react-dom/index.js: 300 B 38 | react/cjs/react.production.min.js: 7 KB 39 | react-dom/cjs/react-dom.production.min.js: 110 KB 40 | scheduler/index.js: 50 B 41 | scheduler/cjs/scheduler.production.min.js: 5 KB 42 | webpack/buildin/global.js: 150 B 43 | : 150 B 44 | - '!precache-manifest*' 45 | - '!service-worker.js' 46 | - '!**/runtime*.js' 47 | - '@modus/gimbal-plugin-last-value' 48 | - plugin: '@modus/gimbal-plugin-mysql' 49 | enabled: ${env:CI, true, false} 50 | commandPrefix: 51 | - react-example-${env:CIRCLE_USERNAME}-${env:CIRCLE_BRANCH} 52 | - react-example-${env:CIRCLE_BRANCH} 53 | - react-example 54 | lastValue: 55 | database: gimbal_github 56 | host: ${env:GIMBAL_MYSQL_HOST} 57 | password: ${env:GIMBAL_MYSQL_PASSWORD} 58 | user: ${env:GIMBAL_MYSQL_USERNAME} 59 | # - plugin: '@modus/gimbal-plugin-sqlite' 60 | # enabled: ${env:CI, true, false} 61 | # commandPrefix: 62 | # - react-example-${env:CIRCLE_USERNAME}-${env:CIRCLE_BRANCH} 63 | # - react-example-${env:CIRCLE_BRANCH} 64 | # - react-example 65 | # lastValue: true 66 | -------------------------------------------------------------------------------- /examples/react/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /examples/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-react", 3 | "version": "1.0.12", 4 | "private": true, 5 | "dependencies": { 6 | "react": "16.8.6", 7 | "react-dom": "16.8.6", 8 | "react-scripts": "3.0.1" 9 | }, 10 | "scripts": { 11 | "start": "SKIP_PREFLIGHT_CHECK=true react-scripts start", 12 | "build": "SKIP_PREFLIGHT_CHECK=true react-scripts build", 13 | "gimbal": "TS_NODE_PROJECT=../../packages/gimbal/tsconfig.json yarn start --cwd ../../packages/gimbal -- --cwd ../../examples/react", 14 | "gimbal:built": "gimbal", 15 | "test": "react-scripts test", 16 | "eject": "react-scripts eject" 17 | }, 18 | "eslintConfig": { 19 | "extends": "react-app" 20 | }, 21 | "browserslist": { 22 | "production": [ 23 | ">0.2%", 24 | "not dead", 25 | "not op_mini all" 26 | ], 27 | "development": [ 28 | "last 1 chrome version", 29 | "last 1 firefox version", 30 | "last 1 safari version" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ModusCreateOrg/gimbal/7cbf580dd113802d205cc4341d7bea112237a39c/examples/react/public/favicon.ico -------------------------------------------------------------------------------- /examples/react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 23 | React App 24 | 25 | 26 | 27 | 28 |
29 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/react/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /examples/react/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .App-header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .App-link { 23 | color: #61dafb; 24 | } 25 | 26 | @keyframes App-logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/react/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | function App() { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.js and save to reload. 12 |

13 | 19 | Learn React 20 | 21 |
22 |
23 | ); 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /examples/react/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /examples/react/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/react/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "npmClient": "yarn", 6 | "useWorkspaces": true, 7 | "version": "1.2.6", 8 | "command": { 9 | "publish": { 10 | "allowBranch": "master" 11 | }, 12 | "version": { 13 | "allowBranch": "master" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modus/gimbal-monorepo", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "bootstrap": "lerna bootstrap", 7 | "build": "lerna run build", 8 | "build:check": "lerna run build:check", 9 | "build:examples": "./bin/build_examples.sh", 10 | "clean": "rimraf '{examples,packages}/**/{build,lib,dist,package-lock.json}' '**/node_modules' '**/coverage'", 11 | "lerna:version": "lerna version patch --exact --force-publish --yes", 12 | "link": "lerna run link", 13 | "lint": "lerna run lint", 14 | "lint-staged": "lint-staged", 15 | "postinstall": "yarn run bootstrap", 16 | "test": "lerna run test --stream", 17 | "test:coveralls": "cat ./coverage/lcov.info | coveralls", 18 | "test:examples": "./bin/test_examples.sh", 19 | "test:nocov": "lerna run test:nocov --stream", 20 | "version:check": "versionator --exclude 'examples/**/package.json'" 21 | }, 22 | "husky": { 23 | "hooks": { 24 | "pre-commit": "yarn run lint-staged" 25 | } 26 | }, 27 | "lint-staged": { 28 | "packages/**/bin/**/*.{js,jsx,ts,tsx},packages/**/src/**/*.{js,jsx,ts,tsx}": [ 29 | "eslint --fix", 30 | "git add" 31 | ] 32 | }, 33 | "workspaces": [ 34 | "packages/*" 35 | ], 36 | "devDependencies": { 37 | "@mitchellsimoens/versionator": "1.0.2", 38 | "@types/jest": "24.0.18", 39 | "@types/node": "12.7.4", 40 | "@typescript-eslint/eslint-plugin": "2.3.3", 41 | "@typescript-eslint/parser": "2.3.3", 42 | "@zerollup/ts-transform-paths": "1.7.3", 43 | "eslint": "6.5.1", 44 | "eslint-config-airbnb": "18.0.1", 45 | "eslint-config-prettier": "6.4.0", 46 | "eslint-import-resolver-lerna": "1.1.0", 47 | "eslint-import-resolver-typescript": "2.0.0", 48 | "eslint-plugin-import": "2.18.2", 49 | "eslint-plugin-jest": "22.17.0", 50 | "eslint-plugin-jsx-a11y": "6.2.3", 51 | "eslint-plugin-prettier": "3.1.1", 52 | "eslint-plugin-react": "7.16.0", 53 | "husky": "3.0.8", 54 | "jest": "24.9.0", 55 | "lerna": "3.17.0", 56 | "lint-staged": "9.4.2", 57 | "prettier": "1.18.2", 58 | "rimraf": "3.0.0", 59 | "ts-jest": "24.1.0", 60 | "ts-node": "8.4.1", 61 | "tsconfig-paths": "3.9.0", 62 | "ttypescript": "1.5.7", 63 | "typescript": "3.6.4" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/gimbal-core/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /packages/gimbal-core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: `${__dirname}/tsconfig.json`, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/gimbal-core/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Core 2 | 3 | [![npm (scoped)](https://img.shields.io/npm/v/@modus/gimbal-core.svg)](https://www.npmjs.com/package/@modus/gimbal-core) 4 | [![npm](https://img.shields.io/npm/dm/@modus/gimbal-core.svg)](https://www.npmjs.com/package/@modus/gimbal-core) 5 | [![CircleCI](https://circleci.com/gh/ModusCreateOrg/gimbal.svg?style=svg&circle-token=070b2e28332dfe71ad3b6b8ab9ee5d472a1d7f76)](https://circleci.com/gh/ModusCreateOrg/gimbal) 6 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 7 | [![MIT Licensed](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](./LICENSE) 8 | [![Powered by Modus_Create](https://img.shields.io/badge/powered_by-Modus_Create-blue.svg?longCache=true&style=flat&logo=data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMzIwIDMwMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8cGF0aCBkPSJNOTguODI0IDE0OS40OThjMCAxMi41Ny0yLjM1NiAyNC41ODItNi42MzcgMzUuNjM3LTQ5LjEtMjQuODEtODIuNzc1LTc1LjY5Mi04Mi43NzUtMTM0LjQ2IDAtMTcuNzgyIDMuMDkxLTM0LjgzOCA4Ljc0OS01MC42NzVhMTQ5LjUzNSAxNDkuNTM1IDAgMCAxIDQxLjEyNCAxMS4wNDYgMTA3Ljg3NyAxMDcuODc3IDAgMCAwLTcuNTIgMzkuNjI4YzAgMzYuODQyIDE4LjQyMyA2OS4zNiA0Ni41NDQgODguOTAzLjMyNiAzLjI2NS41MTUgNi41Ny41MTUgOS45MjF6TTY3LjgyIDE1LjAxOGM0OS4xIDI0LjgxMSA4Mi43NjggNzUuNzExIDgyLjc2OCAxMzQuNDggMCA4My4xNjgtNjcuNDIgMTUwLjU4OC0xNTAuNTg4IDE1MC41ODh2LTQyLjM1M2M1OS43NzggMCAxMDguMjM1LTQ4LjQ1OSAxMDguMjM1LTEwOC4yMzUgMC0zNi44NS0xOC40My02OS4zOC00Ni41NjItODguOTI3YTk5Ljk0OSA5OS45NDkgMCAwIDEtLjQ5Ny05Ljg5NyA5OC41MTIgOTguNTEyIDAgMCAxIDYuNjQ0LTM1LjY1NnptMTU1LjI5MiAxODIuNzE4YzE3LjczNyAzNS41NTggNTQuNDUgNTkuOTk3IDk2Ljg4OCA1OS45OTd2NDIuMzUzYy02MS45NTUgMC0xMTUuMTYyLTM3LjQyLTEzOC4yOC05MC44ODZhMTU4LjgxMSAxNTguODExIDAgMCAwIDQxLjM5Mi0xMS40NjR6bS0xMC4yNi02My41ODlhOTguMjMyIDk4LjIzMiAwIDAgMS00My40MjggMTQuODg5QzE2OS42NTQgNzIuMjI0IDIyNy4zOSA4Ljk1IDMwMS44NDUuMDAzYzQuNzAxIDEzLjE1MiA3LjU5MyAyNy4xNiA4LjQ1IDQxLjcxNC01MC4xMzMgNC40Ni05MC40MzMgNDMuMDgtOTcuNDQzIDkyLjQzem01NC4yNzgtNjguMTA1YzEyLjc5NC04LjEyNyAyNy41NjctMTMuNDA3IDQzLjQ1Mi0xNC45MTEtLjI0NyA4Mi45NTctNjcuNTY3IDE1MC4xMzItMTUwLjU4MiAxNTAuMTMyLTIuODQ2IDAtNS42NzMtLjA4OC04LjQ4LS4yNDNhMTU5LjM3OCAxNTkuMzc4IDAgMCAwIDguMTk4LTQyLjExOGMuMDk0IDAgLjE4Ny4wMDguMjgyLjAwOCA1NC41NTcgMCA5OS42NjUtNDAuMzczIDEwNy4xMy05Mi44Njh6IiBmaWxsPSIjRkZGIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiLz4KPC9zdmc+)](https://moduscreate.com) 9 | 10 | Core utilities for [Modus Gimbal](https://www.npmjs.com/package/@modus/gimbal). 11 | -------------------------------------------------------------------------------- /packages/gimbal-core/jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest/utils'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | module.exports = { 5 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 6 | prefix: '/', 7 | }), 8 | roots: ['/src'], 9 | transform: { 10 | '^.+\\.tsx?$': 'ts-jest', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/gimbal-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modus/gimbal-core", 3 | "version": "1.2.6", 4 | "description": "Core utilities for Modus Gimbal", 5 | "author": "Mitchell Simoens ", 6 | "homepage": "https://github.com/ModusCreateOrg/gimbal#readme", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "files": [ 10 | "lib" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/ModusCreateOrg/gimbal.git" 18 | }, 19 | "scripts": { 20 | "build": "ttsc -p .", 21 | "build:check": "ttsc --noEmit -p .", 22 | "build:watch": "ttsc --watch -p .", 23 | "lint": "eslint 'src/**/*.ts'", 24 | "prepublish": "yarn build", 25 | "test": "jest --coverage", 26 | "test:coveralls": "cat ./coverage/lcov.info | coveralls", 27 | "test:nocov": "jest" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/ModusCreateOrg/gimbal/issues" 31 | }, 32 | "dependencies": { 33 | "cli-table3": "0.5.1", 34 | "colors": "1.4.0", 35 | "commander": "3.0.1", 36 | "deepmerge": "4.0.0", 37 | "get-folder-size": "2.0.1", 38 | "get-port": "5.0.0", 39 | "globby": "10.0.1", 40 | "is-ci": "2.0.0", 41 | "minimatch": "3.0.4", 42 | "mkdirp": "0.5.1", 43 | "pidusage": "2.0.17", 44 | "spinnies": "0.5.0", 45 | "strip-ansi": "5.2.0" 46 | }, 47 | "devDependencies": { 48 | "@types/get-folder-size": "2.0.0", 49 | "@types/globby": "9.1.0", 50 | "@types/is-ci": "2.0.0", 51 | "@types/minimatch": "3.0.3", 52 | "@types/mkdirp": "0.5.2", 53 | "@types/pidusage": "2.0.1" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/components/Table/renderer/html.ts: -------------------------------------------------------------------------------- 1 | import renderMarkdown from './markdown'; 2 | import { RendererArgs } from '@/typings/components/Table'; 3 | 4 | // one day make this more powerful and create real html from a template maybe 5 | const renderHtml = (args: RendererArgs): Promise => renderMarkdown(args); 6 | 7 | export default renderHtml; 8 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/components/Table/renderer/markdown.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import stripAnsi from 'strip-ansi'; 3 | import { Column, RendererArgs } from '@/typings/components/Table'; 4 | import renderCli from './cli'; 5 | 6 | type AlignmentValues = '----' | ':---:' | '---:'; 7 | 8 | interface Alignment { 9 | center: AlignmentValues; 10 | left: AlignmentValues; 11 | right: AlignmentValues; 12 | } 13 | 14 | interface BorderItem { 15 | [name: string]: AlignmentValues; 16 | } 17 | 18 | const alignments: Alignment = { 19 | center: ':---:', 20 | left: '----', 21 | right: '---:', 22 | }; 23 | 24 | const renderMarkdown = async ({ columns, data, options }: RendererArgs): Promise => { 25 | const item: BorderItem = { 26 | label: alignments.left, 27 | rawLabel: alignments.left, 28 | threshold: alignments.center, 29 | success: alignments.center, 30 | value: alignments.center, 31 | }; 32 | 33 | columns.forEach((column: Column): void => { 34 | const char = alignments[column.align || 'left']; 35 | 36 | item[column.key] = char; 37 | }); 38 | 39 | data.unshift(item); 40 | 41 | return stripAnsi(await renderCli({ columns, data, options })); 42 | }; 43 | 44 | export default renderMarkdown; 45 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/components/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable-next-line import/prefer-default-export */ 2 | export { default as Table } from './Table'; 3 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './logger'; 2 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/logger/ci.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable-next-line no-console */ 2 | export const startSpinner = (name: string): void => console.log(` [ ${name} ] starting...`); 3 | 4 | export const finishSpinner = (name: string, success: boolean, output: string): void => 5 | /* eslint-disable-next-line no-console */ 6 | console.log(`${success ? '✓' : '✗'} [ ${name} ] - ${success ? 'Success!' : 'Failed!'} - ${output}`); 7 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/logger/cli.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import Spinnies from 'spinnies'; 3 | import { SpinniesConfig, SpinniesFinish, SpinniesOptions } from '@/typings/logger'; 4 | 5 | export const createSpinner = (options?: SpinniesConfig): Spinnies => new Spinnies(options); 6 | 7 | export const spinnies = createSpinner(); 8 | 9 | const parseSpinnerOptions = (options: string | SpinniesOptions | SpinniesFinish): SpinniesOptions | SpinniesFinish => 10 | typeof options === 'string' ? { text: options } : options; 11 | 12 | export const addSpinner = (name: string, options: string | SpinniesOptions): void => { 13 | const spinnerOptions: SpinniesOptions = parseSpinnerOptions(options); 14 | 15 | if (spinnerOptions.status === 'stopped') { 16 | spinnerOptions.text = ` ${spinnerOptions.text}`; 17 | } 18 | 19 | spinnies.add(name, spinnerOptions); 20 | }; 21 | 22 | export const getSpinner = (name: string): SpinniesOptions => spinnies.pick(name); 23 | 24 | export const updateSpinner = (name: string, options: string | SpinniesOptions): void => { 25 | const oldOptions: SpinniesOptions = getSpinner(name); 26 | const spinnerOptions: SpinniesOptions = parseSpinnerOptions(options); 27 | 28 | if (oldOptions && oldOptions.status === 'stopped' && spinnerOptions.status !== 'stopped') { 29 | spinnerOptions.text = spinnerOptions.text.replace(/^\s+/, ''); 30 | } 31 | 32 | spinnies.update(name, spinnerOptions); 33 | }; 34 | 35 | export const startSpinner = (name: string): void => { 36 | const spinnerOptions: SpinniesOptions = { 37 | ...getSpinner(name), 38 | status: 'spinning', 39 | }; 40 | 41 | updateSpinner(name, spinnerOptions); 42 | }; 43 | 44 | export const finishSpinner = (name: string, success: boolean, output: string, text = ''): void => { 45 | const oldOptions: SpinniesOptions = getSpinner(name); 46 | const newText = oldOptions.text.replace(/^\s+/, ''); 47 | const spinnerOptions: SpinniesFinish = { 48 | ...parseSpinnerOptions(text), 49 | text: `${newText} - ${success ? 'Success!' : 'Failed!'} - ${output}`, 50 | }; 51 | 52 | if (success) { 53 | spinnies.succeed(name, spinnerOptions); 54 | } else { 55 | spinnies.fail(name, spinnerOptions); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/logger/index.ts: -------------------------------------------------------------------------------- 1 | import isCI from 'is-ci'; 2 | import { finishSpinner as finishSpinnerCI, startSpinner as startSpinnerCI } from './ci'; 3 | import { addSpinner as addSpinnerCLI, finishSpinner as finishSpinnerCLI, startSpinner as startSpinnerCLI } from './cli'; 4 | 5 | import { SpinniesOptions } from '@/typings/logger'; 6 | 7 | interface SpinnerTimes { 8 | [name: string]: [number, number]; 9 | } 10 | 11 | const spinnerTimes: SpinnerTimes = {}; 12 | 13 | export const addSpinner = (name: string, options: string | SpinniesOptions): void => 14 | isCI ? undefined : addSpinnerCLI(name, options); 15 | 16 | export const startSpinner = (name: string): void => { 17 | spinnerTimes[name] = process.hrtime(); 18 | 19 | if (isCI) { 20 | startSpinnerCI(name); 21 | } else { 22 | startSpinnerCLI(name); 23 | } 24 | }; 25 | 26 | export const finishSpinner = (name: string, success: boolean, text = ''): void => { 27 | const end = process.hrtime(spinnerTimes[name]); 28 | const nanoseconds = end[0] * 1e9 + end[1]; 29 | const milliseconds = nanoseconds / 1e6; 30 | const output = `${milliseconds.toLocaleString()} ms`; 31 | 32 | delete spinnerTimes[name]; 33 | 34 | if (isCI) { 35 | finishSpinnerCI(name, success, output); 36 | } else if (success) { 37 | finishSpinnerCLI(name, success, output, text); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/Process.ts: -------------------------------------------------------------------------------- 1 | import pidusage from 'pidusage'; 2 | 3 | interface ProcessConfig { 4 | autoStart?: boolean; 5 | } 6 | 7 | interface Stat { 8 | cpu: number; // percentage (from 0 to 100*vcore) 9 | memory: number; // bytes 10 | ppid: number; // PPID 11 | pid: number; // PID 12 | ctime: number; // ms user + system time 13 | elapsed: number; // ms since the start of the process 14 | timestamp: number; // ms since epoch 15 | } 16 | 17 | interface Serialized { 18 | stats: Stat[]; 19 | } 20 | 21 | export default class Process { 22 | private interval?: NodeJS.Timeout; 23 | 24 | private pid: number; 25 | 26 | private stats: Stat[] = []; 27 | 28 | public constructor(pid?: number, config: ProcessConfig = { autoStart: true }) { 29 | this.pid = pid || process.pid; 30 | 31 | if (config.autoStart) { 32 | this.start(); 33 | } 34 | } 35 | 36 | public async capture(): Promise { 37 | const stats = await pidusage(this.pid); 38 | 39 | this.stats.push(stats); 40 | } 41 | 42 | public end(): void { 43 | if (this.interval) { 44 | clearInterval(this.interval); 45 | 46 | this.interval = undefined; 47 | } 48 | 49 | pidusage.clear(); 50 | } 51 | 52 | public serialize(): Serialized { 53 | return { stats: this.stats }; 54 | } 55 | 56 | public start(): void { 57 | const interval = setInterval((): Promise => this.capture(), 500) as unknown; 58 | 59 | this.interval = interval as NodeJS.Timeout; 60 | 61 | this.capture(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/Queue.spec.ts: -------------------------------------------------------------------------------- 1 | import Queue from './Queue'; 2 | 3 | describe('@modus/gimbal-core/utils/Queue', (): void => { 4 | describe('add', (): void => { 5 | it('should add to the queue', async (): Promise => { 6 | const instance = new Queue(); 7 | const spy = jest.fn(); 8 | 9 | instance.add(spy); 10 | 11 | await instance.run(); 12 | 13 | expect(spy).toHaveBeenCalled(); 14 | }); 15 | 16 | it('should add multiple to the queue', async (): Promise => { 17 | const instance = new Queue(); 18 | const spy1 = jest.fn(); 19 | const spy2 = jest.fn(); 20 | 21 | instance.add(spy1, spy2); 22 | 23 | await instance.run(); 24 | 25 | expect(spy1).toHaveBeenCalled(); 26 | expect(spy2).toHaveBeenCalled(); 27 | }); 28 | }); 29 | 30 | describe('run', (): void => { 31 | describe('sequential', (): void => { 32 | it('should run in sequence', async (): Promise => { 33 | const instance = new Queue(); 34 | const spy1 = jest.fn().mockResolvedValue('first'); 35 | const spy2 = jest.fn().mockResolvedValue('second'); 36 | 37 | instance.add(spy1, spy2); 38 | 39 | const ret = await instance.run('data'); 40 | 41 | expect(ret).toEqual(['first', 'second']); 42 | 43 | expect(spy1).toHaveBeenCalledWith('data'); 44 | expect(spy2).toHaveBeenCalledWith('data'); 45 | }); 46 | }); 47 | 48 | describe('parallel', (): void => { 49 | it('should run in parallel', async (): Promise => { 50 | const instance = new Queue({ 51 | mode: 'parallel', 52 | }); 53 | const spy1 = jest.fn().mockResolvedValue('first'); 54 | const spy2 = jest.fn().mockResolvedValue('second'); 55 | 56 | instance.add(spy1, spy2); 57 | 58 | const ret = await instance.run('data'); 59 | 60 | expect(ret).toEqual(['first', 'second']); 61 | 62 | expect(spy1).toHaveBeenCalledWith('data'); 63 | expect(spy2).toHaveBeenCalledWith('data'); 64 | }); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/Queue.ts: -------------------------------------------------------------------------------- 1 | import { Data, Mode } from '@/typings/utils/Queue'; 2 | 3 | interface Config { 4 | mode?: Mode; 5 | } 6 | 7 | class Queue { 8 | private mode: Mode = 'sequential'; 9 | 10 | private queue: Data[] = []; 11 | 12 | public constructor(config: Config = {}) { 13 | if (config.mode) { 14 | this.mode = config.mode; 15 | } 16 | } 17 | 18 | public add(...add: Data[]): void { 19 | this.queue.push(...add); 20 | } 21 | 22 | public run(...data: Data[]): Promise { 23 | const { mode, queue } = this; 24 | 25 | return mode === 'sequential' ? this.runSequential(queue, data) : this.runParallel(queue, data); 26 | } 27 | 28 | private runSequential(queue: Data[], data: Data[]): Promise { 29 | return queue.reduce( 30 | (promise: Promise, fn: Data): Promise => 31 | promise.then( 32 | async (rets: Data[]): Promise => { 33 | const ret = await fn(...data); 34 | 35 | rets.push(ret); 36 | 37 | return rets; 38 | }, 39 | ), 40 | Promise.resolve([]), 41 | ); 42 | } 43 | 44 | private runParallel(queue: Data[], data: Data[]): Promise { 45 | return Promise.all(queue.map((fn: Data): Promise => fn(...data))); 46 | } 47 | } 48 | 49 | export default Queue; 50 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/colors.spec.ts: -------------------------------------------------------------------------------- 1 | import { sectionHeading, successOrFailure } from './colors'; 2 | 3 | describe('@modus/gimbal-core/utils/colors', (): void => { 4 | describe('sectionHeading', (): void => { 5 | it('should bold text', (): void => { 6 | const ret = sectionHeading('this should be bold'); 7 | 8 | expect(ret).toBe('this should be bold'); 9 | }); 10 | }); 11 | 12 | describe('successOrFailure', (): void => { 13 | it('should be green for success', (): void => { 14 | const ret = successOrFailure('this should be green', true); 15 | 16 | expect(ret).toBe('this should be green'); 17 | }); 18 | 19 | it('should be red for failure', (): void => { 20 | const ret = successOrFailure('this should be red', false); 21 | 22 | expect(ret).toBe('this should be red'); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/colors.ts: -------------------------------------------------------------------------------- 1 | import { green, red, bold } from 'colors'; 2 | 3 | export const sectionHeading = (text: string): string => bold(text); 4 | 5 | export const successOrFailure = (item: string, success: boolean): string => (success ? green(item) : red(item)); 6 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/env.spec.ts: -------------------------------------------------------------------------------- 1 | import envOrDefault from './env'; 2 | 3 | const ORIG_ENV = process.env; 4 | 5 | beforeEach((): void => { 6 | process.env = {}; 7 | }); 8 | 9 | afterEach((): void => { 10 | process.env = { ...ORIG_ENV }; 11 | }); 12 | 13 | describe('@modus/gimbal-core/utils/env', (): void => { 14 | describe('envOrDefault', (): void => { 15 | test('should return the value', (): void => { 16 | process.env.foo = 'bar'; 17 | 18 | const ret = envOrDefault('foo'); 19 | 20 | expect(ret).toBe('bar'); 21 | }); 22 | 23 | test('should return true as a boolean', (): void => { 24 | process.env.foo = 'true'; 25 | 26 | const ret = envOrDefault('foo'); 27 | 28 | expect(ret).toBe(true); 29 | }); 30 | 31 | test('should return false as a boolean', (): void => { 32 | process.env.foo = 'false'; 33 | 34 | const ret = envOrDefault('foo'); 35 | 36 | expect(ret).toBe(false); 37 | }); 38 | 39 | test('should return default value', (): void => { 40 | const ret = envOrDefault('foo', 'baz'); 41 | 42 | expect(ret).toBe('baz'); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 2 | const envOrDefault = (variableName: string, defaultValue?: any): any => { 3 | const envValue = process.env[variableName]; 4 | 5 | // parse booleans by default 6 | if (envValue === 'true') { 7 | return true; 8 | } 9 | 10 | if (envValue === 'false') { 11 | return false; 12 | } 13 | 14 | if (envValue === undefined) { 15 | return defaultValue; 16 | } 17 | 18 | return envValue; 19 | }; 20 | 21 | export default envOrDefault; 22 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/fs.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import mkdirpMod from 'mkdirp'; 3 | import path from 'path'; 4 | import { promisify } from 'util'; 5 | import getSize from 'get-folder-size'; 6 | 7 | export const getDirectorySize = promisify(getSize); 8 | export const mkdirp = promisify(mkdirpMod); 9 | export const readDir = promisify(fs.readdir); 10 | export const readFile = promisify(fs.readFile); 11 | export const stats = promisify(fs.stat); 12 | export const exists = promisify(fs.exists); 13 | export const writeFile = promisify(fs.writeFile); 14 | 15 | export const resolvePath = (...paths: string[]): string => { 16 | if (paths.length) { 17 | if (paths[0][0] === '~') { 18 | return `${process.env.HOME}${paths[0].slice(1)}`; 19 | } 20 | 21 | return path.resolve(...paths); 22 | } 23 | 24 | return process.cwd(); 25 | }; 26 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/port.ts: -------------------------------------------------------------------------------- 1 | import getPort from 'get-port'; 2 | 3 | const findPort = async (): Promise => { 4 | const port = await getPort({ 5 | port: getPort.makeRange(3000, 3100), 6 | }); 7 | 8 | return port; 9 | }; 10 | 11 | export default findPort; 12 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/spawn.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | import { CmdSpawnOptions, CmdSpawnRet } from '@/typings/utils/spawn'; 3 | import OS from './Process'; 4 | 5 | interface CmdSpawnConfig { 6 | noUsage?: boolean; 7 | } 8 | 9 | const cmdSpawn = (args: string[], options?: CmdSpawnOptions, config: CmdSpawnConfig = {}): Promise => { 10 | return new Promise((resolve, reject): void => { 11 | const start = new Date(); 12 | 13 | const spawned = spawn(args[0], args.slice(1), { 14 | cwd: process.cwd(), 15 | env: process.env, 16 | timeout: 5 * 1000 * 60, // 5 minutes 17 | ...options, 18 | }); 19 | const os = config.noUsage ? undefined : new OS(spawned.pid); 20 | const logs: Buffer[] = []; 21 | 22 | if (spawned.stderr && spawned.stdout) { 23 | const onLog = (data: Buffer): void => { 24 | if (os) { 25 | os.capture(); 26 | } 27 | 28 | logs.push(data); 29 | }; 30 | 31 | spawned.stderr.on('data', (data: Buffer): void => onLog(data)); 32 | spawned.stdout.on('data', (data: Buffer): void => onLog(data)); 33 | } 34 | 35 | spawned.on('close', (code: number): void => { 36 | if (os) { 37 | os.end(); 38 | } 39 | 40 | const end = new Date(); 41 | 42 | const ret: CmdSpawnRet = { 43 | code, 44 | end, 45 | logs, 46 | os, 47 | start, 48 | success: code === 0, 49 | }; 50 | 51 | if (code) { 52 | reject(ret); 53 | } else { 54 | resolve(ret); 55 | } 56 | }); 57 | }); 58 | }; 59 | 60 | export default cmdSpawn; 61 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/string.spec.ts: -------------------------------------------------------------------------------- 1 | import { pad, splitOnWhitespace } from './string'; 2 | 3 | describe('@modus/gimbal-core/utils/string', (): void => { 4 | describe('pad', (): void => { 5 | it('should pad a string', (): void => { 6 | const ret = pad(4); 7 | 8 | expect(ret).toBe(' '); 9 | }); 10 | 11 | it('should pad a string with a character', (): void => { 12 | const ret = pad(4, '-'); 13 | 14 | expect(ret).toBe('----'); 15 | }); 16 | }); 17 | 18 | describe('splitOnWhitespace', (): void => { 19 | it('should split', (): void => { 20 | const ret = splitOnWhitespace('hello there'); 21 | 22 | expect(ret).toEqual(['hello', 'there']); 23 | }); 24 | 25 | it('should split with lots of spaces', (): void => { 26 | const ret = splitOnWhitespace(' hello there '); 27 | 28 | expect(ret).toEqual(['hello', 'there']); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/string.ts: -------------------------------------------------------------------------------- 1 | export const pad = (amount: number, character = ' '): string => new Array(amount).fill(character).join(''); 2 | export const splitOnWhitespace = (str: string): string[] => str.trim().split(/\s+/); 3 | -------------------------------------------------------------------------------- /packages/gimbal-core/src/utils/threshold.ts: -------------------------------------------------------------------------------- 1 | type Modes = 'lower' | 'upper'; 2 | 3 | const PERCENTAGE_RE = /^(\d+(?:\.\d+)?)\s*%$/; 4 | 5 | export const isPercentage = (value: number | string): boolean => String(value).match(PERCENTAGE_RE) != null; 6 | export const percentToNumber = (value: string): number => Number(value.replace(PERCENTAGE_RE, '$1')); 7 | 8 | export const checkValue = (value: number, threshold: number, mode: Modes): boolean => 9 | mode === 'lower' 10 | ? value >= threshold // lower means value should be above or equal to the threshold 11 | : value <= threshold; // upper mode means value should be below or equal to the threshold 12 | 13 | export const checkPercentage = (raw: string, rawThreshold: string, mode: Modes): boolean => { 14 | const threshold = percentToNumber(rawThreshold); 15 | const value = percentToNumber(raw); 16 | 17 | return checkValue(value, threshold, mode); 18 | }; 19 | 20 | const checkThreshold = (value: number | string, threshold: number | string, mode: Modes = 'upper'): boolean => 21 | isPercentage(value) 22 | ? checkPercentage(value as string, threshold as string, mode) 23 | : checkValue(value as number, threshold as number, mode); 24 | 25 | export default checkThreshold; 26 | -------------------------------------------------------------------------------- /packages/gimbal-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDirs": ["src"], 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/typings/*": ["../typings/*"] 9 | } 10 | }, 11 | "exclude": [ 12 | "src/**/*.spec.ts" 13 | ], 14 | "include": [ 15 | "src" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/gimbal/.dockerignore: -------------------------------------------------------------------------------- 1 | build 2 | typings 3 | -------------------------------------------------------------------------------- /packages/gimbal/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /packages/gimbal/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: `${__dirname}/tsconfig.eslint.json`, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/gimbal/.gitignore: -------------------------------------------------------------------------------- 1 | /sample/ 2 | -------------------------------------------------------------------------------- /packages/gimbal/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos:7 AS Gimbal 2 | 3 | ARG GIMBAL_VERSION 4 | 5 | # Install packages 6 | RUN yum update -y \ 7 | && yum install -y gcc-c++ make curl libX11-devel libXcomposite libXcursor libXdamage \ 8 | && yum clean all \ 9 | && curl -sL https://rpm.nodesource.com/setup_10.x | bash - \ 10 | && yum install -y nodejs \ 11 | && npm install -g --unsafe-perm=true --allow-root @modus/gimbal@${GIMBAL_VERSION} 12 | 13 | RUN yum install ipa-gothic-fonts xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc libXext cups-libs libXScrnSaver -y 14 | 15 | RUN yum install alsa-lib pango -y 16 | 17 | RUN yum install pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 -y 18 | 19 | CMD ["/bin/sh"] 20 | -------------------------------------------------------------------------------- /packages/gimbal/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011-present, Modus Create, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/gimbal/bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // eslint-disable-next-line import/no-unresolved 4 | require('../lib/gimbal/src'); 5 | -------------------------------------------------------------------------------- /packages/gimbal/docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Prepare a clean environment 3 | 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | 7 | # Enable for enhanced debugging 8 | #set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | BASE="$DIR/.." 15 | 16 | cd "$BASE" || exit 1 17 | 18 | VERSION=`grep -o "\"version\": \".*\"," $BASE/package.json | grep -o "\d*\.\d*\.\d*"` 19 | 20 | docker build \ 21 | -t "gimbal:$VERSION" \ 22 | -t "moduscreate/gimbal:$VERSION" \ 23 | --build-arg GIMBAL_VERSION="$VERSION" \ 24 | . 25 | -------------------------------------------------------------------------------- /packages/gimbal/docker/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Prepare a clean environment 3 | 4 | # Set bash unofficial strict mode http://redsymbol.net/articles/unofficial-bash-strict-mode/ 5 | set -euo pipefail 6 | 7 | # Enable for enhanced debugging 8 | #set -vx 9 | # Credit to https://stackoverflow.com/a/17805088 10 | # and http://wiki.bash-hackers.org/scripting/debuggingtips 11 | export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }' 12 | 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | BASE="$DIR/.." 15 | 16 | RAW_APP="${1:-./sample}" 17 | APP_DIR="$( cd "$BASE/$RAW_APP" && pwd )" 18 | 19 | VERSION=`grep -o "\"version\": \".*\"," $BASE/package.json | grep -o "\d*\.\d*\.\d*"` 20 | 21 | docker run -v "$APP_DIR":/app -w /app -it "gimbal:$VERSION" gimbal 22 | -------------------------------------------------------------------------------- /packages/gimbal/docs/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Documentation 2 | 3 | Thanks for visiting the documentation for Gimbal! Here we'll discuss in more depth how to use Gimbal, how to configure it and even how to work on Gimbal itself. 4 | 5 | ## Architecture 6 | 7 | Gimbal is split up into different chunks: 8 | 9 | - [ci](./ci): Gimbal will be executed frequently in different CIs and we may need to detect which CI we are running in. 10 | - [command](./command): This is the entry point into running things within Gimbal. 11 | - [config](./config): Handles loading and parsing configuration files. 12 | - [event](./event): Gimbal fires events internally that allows other things to listen to and enact on. 13 | - [logger](./logger): Gimbal logs messages out and the logger is what handles this. 14 | - [module](./module): Modules are the actual things that run different audits. 15 | - [output](./output): As the name suggests, these will output to the cli and to files on disk. 16 | - [utils](./utils): A location of other utilities. 17 | - [vcs](./vcs): Like `ci`, this is where we can autodetect and enact on things on a per VCS basis. 18 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal CI Integrations 2 | 3 | Gimbal may be executed within a [Continuous Integration](https://en.wikipedia.org/wiki/Continuous_integration) (CI) service. Each service has it's own differences and Gimbal abstracts those differences making it easy to work within different CIs. These are the currently support CI services and links to their individual documentation pages: 4 | 5 | - [Bitbucket Pipelines](./bitbucket) 6 | - [CircleCI](./circleci) 7 | - [TravisCI](./travisci) 8 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/bitbucket/README.md: -------------------------------------------------------------------------------- 1 | # Bitbucket Pipelines Integration 2 | 3 | This example is executing `gimbal audit` considering you don't have a [`.gimbalrc.yml`](../../README.md#Configuration). 4 | If you do so, you may just use `gimbal` instead. 5 | 6 | Considering you already have a bitbucket pipeline running, 7 | you may add gimbal to your workflow with this **[bitbucket-pipelines.yml](bitbucket-pipelines.yml)** example. 8 | 9 | You may also check this [gimbal only version](bitbucket-pipelines_gimbal-only.yml) 10 | or this [more complete example](bitbucket-pipelines_complete.yml) 11 | to get more involved how gimbal could be integrated in a more complex CI flow. 12 | 13 | *Links* 14 | 15 | 1. [Default bitbucket-pipelines.yml example](bitbucket-pipelines.yml) 16 | 2. [Gimbal only bitbucket-pipelines.yml example](bitbucket-pipelines_gimbal-only.yml) 17 | 3. [More CI complete bitbucket-pipelines.yml example](bitbucket-pipelines_complete.yml) 18 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/bitbucket/bitbucket-pipelines.yml: -------------------------------------------------------------------------------- 1 | image: node:10.15.3 2 | 3 | definitions: 4 | steps: 5 | ###### 6 | # This step definition emulates your actual install / build process 7 | # This is what you must have already 8 | ###### 9 | - step: &step-install 10 | name: Install 11 | caches: 12 | - node 13 | script: 14 | - npm install 15 | - npm run-script build 16 | artifacts: 17 | ####### 18 | # Important to save the build to artifacts 19 | # so Gimbal step can retrieve the build 20 | # 21 | # You should set your build folder here 22 | ####### 23 | - your-build-folder/** 24 | 25 | - step: &step-gimbal 26 | name: Gimbal Audit 27 | # use the gimbal docker container for this step only 28 | image: moduscreate/gimbal:1.2-latest 29 | script: 30 | - gimbal audit 31 | artifacts: 32 | - gimbal-artifacts/** 33 | 34 | pipelines: 35 | default: 36 | - step: *step-install 37 | - step: *step-gimbal 38 | branches: 39 | master: 40 | - step: *step-install 41 | - step: *step-gimbal 42 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/bitbucket/bitbucket-pipelines_complete.yml: -------------------------------------------------------------------------------- 1 | image: node:10.15.3 2 | 3 | # Breaking these out allows reuse across pipelines 4 | definitions: 5 | caches: 6 | yarn: /usr/local/share/.cache/yarn/v4 7 | services: 8 | docker: 9 | memory: 4096 10 | steps: 11 | - step: &step-install 12 | name: Install 13 | caches: 14 | - node 15 | - yarn 16 | script: 17 | - yarn 18 | # triggers caching for our custom yarn cache 19 | - yarn cache dir 20 | - step: &step-build 21 | name: Build 22 | caches: 23 | - node 24 | - yarn 25 | script: 26 | - yarn 27 | # likely just `yarn build` but keeping it for reference 28 | - yarn build:frontend 29 | artifacts: 30 | ####### 31 | # Important to save the build to artifacts 32 | # so Gimbal step can retrieve the build 33 | ####### 34 | - frontend/build/** 35 | - step: &step-lint 36 | name: Lint 37 | services: 38 | - docker 39 | caches: 40 | - node 41 | - yarn 42 | script: 43 | - yarn 44 | - yarn lint 45 | - step: &step-test 46 | name: Unit Tests 47 | caches: 48 | - node 49 | - yarn 50 | script: 51 | - yarn 52 | - yarn test 53 | - step: &step-gimbal 54 | name: Gimbal Audit 55 | # use the gimbal docker container for this step only 56 | image: moduscreate/gimbal:1.2-latest 57 | caches: 58 | - node 59 | - yarn 60 | script: 61 | - gimbal audit 62 | artifacts: 63 | - gimbal-artifacts/** 64 | 65 | options: 66 | size: 2x 67 | 68 | pipelines: 69 | default: 70 | - step: *step-install 71 | - parallel: 72 | - step: *step-build 73 | - step: *step-lint 74 | - step: *step-test 75 | # cannot be parallel since it depends on the build 76 | - step: *step-gimbal 77 | branches: 78 | master: 79 | - step: *step-install 80 | - parallel: 81 | - step: *step-build 82 | - step: *step-lint 83 | - step: *step-test 84 | # cannot be parallel since it depends on the build 85 | - step: *step-gimbal 86 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/bitbucket/bitbucket-pipelines_gimbal-only.yml: -------------------------------------------------------------------------------- 1 | image: moduscreate/gimbal:latest 2 | 3 | pipelines: 4 | default: 5 | - step: 6 | name: Gimbal audit 7 | script: 8 | - npm install 9 | - npm run-script build 10 | - gimbal audit 11 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/circleci/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal CircleCI Integration 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/circleci/sample.yml: -------------------------------------------------------------------------------- 1 | aliases: 2 | - &restore-npm-cache 3 | keys: 4 | - v1-dependencies-{{ checksum "package-lock.json" }} 5 | - v1-dependencies-master 6 | - v1-dependencies- 7 | - &restore-webpack-cache 8 | keys: 9 | - v1-webpackcache-{{ checksum "package.json" }} 10 | - v1-webpackcache-master 11 | - v1-webpackcache- 12 | - &restore-dist-cache 13 | keys: 14 | - v1-dist-{{ .Environment.CIRCLE_SHA1 }} 15 | - v1-dist-master 16 | - v1-dist- 17 | 18 | defaults: &defaults 19 | # this is key for restoring cache using different docker images 20 | # need to use full path, not using ~ as user home is different 21 | working_directory: /home/circleci/project/ 22 | docker: 23 | - image: circleci/node:10 24 | 25 | version: 2.1 26 | jobs: 27 | install-dependencies: 28 | <<: *defaults 29 | steps: 30 | - checkout 31 | - restore_cache: *restore-npm-cache 32 | - run: 33 | name: Install Node modules 34 | command: npm i -ci 35 | - save_cache: 36 | key: v1-dependencies-{{ checksum "package-lock.json" }} 37 | paths: 38 | - node_modules 39 | 40 | build: 41 | <<: *defaults 42 | steps: 43 | - checkout 44 | - restore_cache: *restore-npm-cache 45 | - restore_cache: *restore-webpack-cache 46 | - run: 47 | name: Build Project 48 | command: npm run build 49 | - save_cache: 50 | key: v1-dist-{{ .Environment.CIRCLE_SHA1 }} 51 | paths: 52 | - build 53 | - save_cache: 54 | key: v1-webpackcache-{{ checksum "package.json" }} 55 | paths: 56 | - node_modules/.cache 57 | 58 | gimbal: 59 | <<: *defaults 60 | docker: 61 | - image: moduscreate/gimbal:1.0.0 62 | steps: 63 | - checkout 64 | - restore_cache: *restore-dist-cache 65 | - run: 66 | name: Run Gimbal 67 | command: gimbal 68 | - store_artifacts: 69 | path: ./artifacts 70 | 71 | workflows: 72 | version: 2 73 | build-and-test: 74 | jobs: 75 | - install-dependencies 76 | - build: 77 | requires: 78 | - install-dependencies 79 | - gimbal: 80 | requires: 81 | - build 82 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/github/README.md: -------------------------------------------------------------------------------- 1 | # GitHub Actions 2 | 3 | Simply add the following YML section in your Actions job: 4 | 5 | ```yml 6 | - name: Performance Budgeting Regression Testing 7 | uses: ModusCreateOrg/gimbal/action@master 8 | env: 9 | GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 10 | ``` 11 | 12 | 13 | Use the [sample workflow](./workflow.yml) to learn how to integrate Gimbal with GitHub Actions. 14 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/github/workflow.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | Test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check out the code 10 | uses: actions/checkout@v1 11 | with: 12 | fetch-depth: 1 13 | - name: Install node 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: 12.x 17 | - name: Install npm dependencies 18 | run: npm i 19 | - name: Build 20 | run: npm run build 21 | - name: Performance Budgeting Regression Testing 22 | uses: ModusCreateOrg/gimbal/action@master 23 | env: 24 | GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/travisci/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Travis CI Integration 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /packages/gimbal/docs/ci/travisci/sample.yml: -------------------------------------------------------------------------------- 1 | # must be xenial if xvfb is wanted per https://docs.travis-ci.com/user/gui-and-headless-browsers/#using-services 2 | dist: xenial 3 | 4 | services: 5 | - xvfb 6 | 7 | language: node_js 8 | 9 | node_js: 10 | - 10.15.3 11 | 12 | before_install: 13 | - npm install @modus/gimbal 14 | 15 | env: 16 | matrix: 17 | - GITHUB_AUTH_TOKEN= 18 | 19 | jobs: 20 | include: 21 | - stage: test 22 | name: "Execute Gimbal" 23 | script: 24 | - npm install 25 | - npm run build 26 | - gimbal 27 | -------------------------------------------------------------------------------- /packages/gimbal/docs/command/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Commands 2 | 3 | The follow are the currently supported commands and links to their individual documentation pages: 4 | 5 | - [audit](./audit) 6 | - [heap-snapshot](./heap-snapshot) 7 | - [lighthouse](./lighthouse) 8 | - [size](./size) 9 | - [unused-source](./unused-source) 10 | -------------------------------------------------------------------------------- /packages/gimbal/docs/command/audit/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal `audit` Command 2 | 3 | The `audit` command, by default, runs the [`heap-snapshot`](../../module/heap-snapshot), [`lighthouse`](../../module/lighthouse), [`size`](../../module/size) and [`unused-source`](../../module/unused-source) modules aimed for an application generated by the [create-react-app](https://facebook.github.io/create-react-app/) tool. 4 | 5 | ## Usage 6 | 7 | To use, simply execute `gimbal audit` in the root of your `create-react-app` generated project. 8 | 9 | **NOTE** You should build the project prior to running Gimbal on it as this will test the built application. 10 | 11 | ## Options 12 | 13 | You can opt-out of running a module via these cli flags: 14 | 15 | - `--no-heap-snapshot` Disabled the `heap-snapshot` module. 16 | - `--no-lighthouse` Disabled the `lighthouse` module. 17 | - `--no-size` Disabled the `size` module. 18 | - `--no-calculate-unused-source` Disabled the `unused-source` module. 19 | 20 | The following are other options: 21 | 22 | - `--build-dir [dir]` By default, the build directory is specified as `build` but you can change it. This path is relative to the current working directory. 23 | - `--no-check-thresholds` by default, Gimbal will check values against thresholds. Setting this option will disable threshold checks. 24 | -------------------------------------------------------------------------------- /packages/gimbal/docs/command/heap-snapshot/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal `heap-snapshot` Command 2 | 3 | The `heap-snapshot` command will execute the [`heap-snapshot`](../../module/heap-snapshot) module. It is recommended to run this command on an already built application. 4 | 5 | ## Usage 6 | 7 | To run, execute `gimbal heap-snapshot` in the directory that contains the build. 8 | 9 | ## Options 10 | 11 | - `--no-check-thresholds` by default, Gimbal will check values against thresholds. Setting this option will disable threshold checks. 12 | -------------------------------------------------------------------------------- /packages/gimbal/docs/command/lighthouse/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal `lighthouse` Command 2 | 3 | The `lighthouse` command will execute the [`lighthouse`](../../module/lighthouse) module. It is recommended to run this command on an already built application. 4 | 5 | ## Usage 6 | 7 | To run, execute `gimbal lighthouse` in the directory that contains the build. 8 | 9 | ## Options 10 | 11 | - `--no-check-thresholds` by default, Gimbal will check values against thresholds. Setting this option will disable threshold checks. 12 | -------------------------------------------------------------------------------- /packages/gimbal/docs/command/size/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal `size` Command 2 | 3 | The `size` command will execute the [`size`](../../module/size) module. It is recommended to run this command on an already built application. 4 | 5 | ## Usage 6 | 7 | To run, execute `gimbal size` in the root directory of your application. 8 | 9 | ## Options 10 | 11 | - `--no-check-thresholds` by default, Gimbal will check values against thresholds. Setting this option will disable threshold checks. 12 | -------------------------------------------------------------------------------- /packages/gimbal/docs/command/unused-source/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal `unused-source` Command 2 | 3 | The `unused-source` command will execute the [`unused-source`](../../module/unused-source) module. It is recommended to run this command on an already built application. 4 | 5 | ## Usage 6 | 7 | To run, execute `gimbal unused-source` in the directory that contains the build. 8 | 9 | ## Options 10 | 11 | - `--no-check-thresholds` by default, Gimbal will check values against thresholds. Setting this option will disable threshold checks. 12 | -------------------------------------------------------------------------------- /packages/gimbal/docs/config/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Configuration 2 | 3 | Gimbal aims to make auditing an application as simple and as configuration free as possible but no two applications are exactly the same. Gimbal provides the mechanism to easily configure it using a configuration file. 4 | 5 | ## Usage 6 | 7 | To use a configuration file, where gimbal is being executed at should contain a `.gimbalrc.yml`, `.gimbalrc.yaml`, `.gimbalrc.json`, or `.gimbalrc.js` file. Here are some examples: 8 | 9 | ### YAML 10 | 11 | ```yaml 12 | configs: 13 | puppeteer: 14 | headless: false 15 | 16 | outputs: 17 | json: ./artifact/gimbal.json 18 | ``` 19 | 20 | ### JSON 21 | 22 | ```json 23 | { 24 | "configs": { 25 | "puppeteer": { 26 | "headless": false 27 | } 28 | }, 29 | "outputs": { 30 | "json": "./artifact/gimbal.json" 31 | } 32 | } 33 | ``` 34 | 35 | ### JavaScript (simple) 36 | 37 | ```javascript 38 | module.exports = { 39 | configs: { 40 | puppeteer: { 41 | headless: false, 42 | }, 43 | }, 44 | outputs: { 45 | json: './artifact/gimbal.json', 46 | }, 47 | }; 48 | ``` 49 | 50 | ### JavaScript (functional/async) 51 | 52 | ```javascript 53 | module.exports = async () => { 54 | const configs = await getGimbalConfigs(); 55 | const outputs = await getGimbalOutputs(); 56 | 57 | return { 58 | configs, 59 | outputs, 60 | }; 61 | }; 62 | ``` 63 | 64 | ### Lighthouse config 65 | 66 | You can pass [Lighthouse configuration](https://github.com/GoogleChrome/lighthouse/blob/master/docs/configuration.md) straight to the `lighthouse` object: 67 | 68 | ```yaml 69 | configs: 70 | lighthouse: 71 | maxWaitForFcp: 120000; 72 | outputHtml: artifacts/lighthouse.html 73 | ``` 74 | 75 | ## Audits 76 | 77 | A configuration file can specify what specific audits to run. With this, you can just run `gimbal` in the directory where the configuration file is in and it will only run those audits. For example, if you wanted to run the lighthouse and size audits, you would specify the `audits` array: 78 | 79 | ```yaml 80 | audits: 81 | - lighthouse 82 | - size 83 | ``` 84 | 85 | ## Jobs 86 | 87 | A configuration file can also specify what commands to run. This means, if you have a configuration file that specifies what jobs, then all you need to do is execute `gimbal` in that directory and Gimbal will run those commands for you. Example: 88 | 89 | ```yaml 90 | jobs: 91 | - audit 92 | - size --cwd ./foo/bar 93 | ``` 94 | -------------------------------------------------------------------------------- /packages/gimbal/docs/event/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Event System 2 | 3 | Gimbal provides a dynamic and flexible event system. Gimbal fires events that allows anything to get notified and take action on certain parts of the Gimbal execution. 4 | 5 | ## Event Listening 6 | 7 | Events are listenable using an event name, a function and optionally an options object. The event name can be a glob pattern allowing to listen to a flexible array of events. 8 | 9 | ### Simple example 10 | 11 | ```javascript 12 | const Event = require('@modus/gimbal/event'); 13 | 14 | Event.on('some-event', (event, data) => { 15 | console.log('An event fired with a name:', event); 16 | console.log('Event Data:', JSON.stringify(data, null, 2)); 17 | }); 18 | ``` 19 | 20 | ### Example with Options 21 | 22 | ```javascript 23 | const Event = require('@modus/gimbal/event'); 24 | 25 | Event.on('all/foo/*/events', { 26 | priority: -10, 27 | fn: (event, data) => { 28 | console.log('An event fired with a name:', event); 29 | console.log('Event Data:', JSON.stringify(data, null, 2)); 30 | }, 31 | }); 32 | ``` 33 | 34 | Currently, `priority` is the only option supported. The lower the number, the higher the priority because 0 comes before 10. 35 | 36 | ### ASync Example 37 | 38 | Listeners are executed sequentially and if one returns a promise (or uses async/await) then subsequent listeners will only execute when that promise is resolved. 39 | 40 | ```javascript 41 | const Event = require('@modus/gimbal/event'); 42 | 43 | Event.on('all/foo/*/events', { 44 | priority: -10, 45 | fn: async (event, data) => { 46 | const ret = await makeSomeAsyncRequest(data); 47 | 48 | console.log('An event fired with a name:', event); 49 | console.log('Event Data:', JSON.stringify(data, null, 2)); 50 | console.log('Response:', JSON.stringify(ret, null, 2)); 51 | }, 52 | }); 53 | ``` 54 | 55 | ## Event Firing 56 | 57 | To fire an event, you provide the event system a name and data. The data can be anything and is shared among all listeners so if one listener changes the data, the subsequent listeners will get the changes. Firing an event can be waited on all listeners to respond in case they are executed async. 58 | 59 | ```javascript 60 | const Event = require('@modus/gimbal/event'); 61 | 62 | (async () => { 63 | const data = {}; 64 | 65 | const ret = await Event.fire('all/foo/bar/events', data); 66 | 67 | // ret.data === data 68 | // ret.rets === returns from all listeners 69 | })(); 70 | ``` 71 | -------------------------------------------------------------------------------- /packages/gimbal/docs/module/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Modules 2 | 3 | Modules in Gimbal are the things that actually execute the different auditing. Modules should be executed from [commands](../command) or other modules. Below is a list of the current modules and a link to their individual documentation pages: 4 | 5 | - [chrome](./chrome) 6 | - [heap-snapshot](./heap-snapshot) 7 | - [lighthouse](./lighthouse) 8 | - [serve](./serve) 9 | - [size](./size) 10 | - [unused-source](./unused-source) 11 | -------------------------------------------------------------------------------- /packages/gimbal/docs/module/chrome/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Chrome Module 2 | 3 | Gimbal uses the [puppeteer](https://pptr.dev/) project to launch a headless Chrome instance. This is meant to only be used by modules or commands to start a browser in order for audits that require a browser. 4 | 5 | ## Configuration 6 | 7 | Gimbal can accept any configurations that puppeteer's [`launch`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions) method accepts. The `configs.puppeteer` namespace should be used within a configuration file: 8 | 9 | ### YAML 10 | 11 | ```yaml 12 | configs: 13 | puppeteer: 14 | headless: false 15 | ``` 16 | 17 | ### JSON 18 | 19 | ```json 20 | { 21 | "configs": { 22 | "puppeteer": { 23 | "headless": false 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | ### JavaScript 30 | 31 | ```javascript 32 | module.exports = { 33 | configs: { 34 | puppeteer: { 35 | headless: false, 36 | }, 37 | }, 38 | }; 39 | ``` 40 | 41 | ### Default Configuration 42 | 43 | ```json 44 | { 45 | "args": ["--no-sandbox", "–-disable-setuid-sandbox"] 46 | } 47 | ``` 48 | -------------------------------------------------------------------------------- /packages/gimbal/docs/module/heap-snapshot/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Heap-Snapshot Module 2 | 3 | Thanks to using puppeteer in Gimbal, puppeteer is able to get metrics about the page. This information includes heap snapshot information in order to view how much memory the page is taking up along with how many nodes are in the DOM and even how many style recalculations occurred. 4 | 5 | ## Configuration 6 | 7 | The [`metrics`](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagemetrics) method that puppeteer exposes doesn't have any configuration. 8 | 9 | ## Thresholds 10 | 11 | The metrics that the heap-snapshot module retrieves can, optionally, all be checked to be under a threshold. This means you can fail a run if any of the metrics goes above the configured threshold. Here is an example of a complete configuration: 12 | 13 | ```yaml 14 | configs: 15 | heap-snapshot: 16 | threshold: 17 | Documents: 5 18 | Frames: 2 19 | JSHeapTotalSize: 200000 20 | JSHeapUsedSize: 100000 21 | LayoutCount: 5 22 | Nodes: 75 23 | RecalcStyleCount: 4 24 | ``` 25 | 26 | The default threshold is: 27 | 28 | ```yaml 29 | configs: 30 | heap-snapshot: 31 | threshold: 32 | Documents: 5 33 | Frames: 2 34 | LayoutCount: 5 35 | Nodes: 75 36 | RecalcStyleCount: 4 37 | ``` 38 | -------------------------------------------------------------------------------- /packages/gimbal/docs/module/lighthouse/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Lighthouse Module 2 | 3 | The Google Lighthouse project aims at auditing a web application. Gimbal uses it as one of it's modules of performance measuring. 4 | 5 | ## Configuration 6 | 7 | The configuration can accept any configuration that Lighthouse has as described [here](https://github.com/GoogleChrome/lighthouse/blob/master/docs/configuration.md). The `configs.lighthouse` namespace should be used within a configuration file: 8 | 9 | ### YAML 10 | 11 | ```yaml 12 | configs: 13 | lighthouse: 14 | extends: lighthouse:default 15 | ``` 16 | 17 | ### JSON 18 | 19 | ```json 20 | { 21 | "configs": { 22 | "lighthouse": { 23 | "extends": "lighthouse:default" 24 | } 25 | } 26 | } 27 | ``` 28 | 29 | ### JavaScript 30 | 31 | ```javascript 32 | module.exports = { 33 | configs: { 34 | lighthouse: { 35 | extends: 'lighthouse:default', 36 | }, 37 | }, 38 | }; 39 | ``` 40 | 41 | ### Default Configuration 42 | 43 | ```json 44 | { 45 | "extends": "lighthouse:default", 46 | "settings": { 47 | "skipAudits": ["uses-http2", "redirects-http", "uses-long-cache-ttl"] 48 | } 49 | } 50 | ``` 51 | 52 | The reason why it skips these audits is that the Lighthouse module isn't going to be running against your production environment but only a local simple web server and therefor these audits are meaningless. 53 | 54 | ## Thresholds 55 | 56 | Each category of the Lighthouse report can be configured with 57 | 58 | ```yaml 59 | configs: 60 | lighthouse: 61 | threshold: 62 | accessibility: 75, 63 | best-practices: 95, 64 | performance: 50, 65 | pwa: 50, 66 | seo: 90, 67 | ``` 68 | 69 | The default threshold is: 70 | 71 | ```yaml 72 | configs: 73 | lighthouse: 74 | threshold: 75 | accessibility: 75, 76 | best-practices: 95, 77 | performance: 50, 78 | pwa: 50, 79 | seo: 90, 80 | ``` 81 | -------------------------------------------------------------------------------- /packages/gimbal/docs/module/serve/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Serve Module 2 | 3 | The `serve` module is what is used to host a web application in order to run certain audits that use puppeteer to load a web page. 4 | -------------------------------------------------------------------------------- /packages/gimbal/docs/module/unused-source/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Unused-Source Module 2 | 3 | The `unused-source` module is aimed to find CSS and JavaScript code that is unused. When a page is loaded, puppeteer returns ranges of all the CSS and JavaScript assets that tells Gimbal how much of each is used. 4 | 5 | ## Configuration 6 | 7 | There are no configurations for this module. 8 | 9 | ## Thresholds 10 | 11 | Using a glob syntax, assets are matched using an array of objects with `path` and `maxSize` thresholds. The `maxSize` threshold is the percentage of unused source for each asset. 12 | 13 | An object can also take a `type` configuration to be able to properly match different sources in the same file. For example, there could be inline JavaScript and CSS in a single HTML page. If you have the same path for two objects, it's important the order they are in the array as the first match will be used. 14 | 15 | ### Default Threshold 16 | 17 | ```yaml 18 | configs: 19 | unused-source: 20 | threshold: 21 | - path: '**/*/*.css' 22 | maxSize: 30% 23 | - path: '**/*/main.*.js' 24 | maxSize: 2% 25 | - path: '**/*/*.js' 26 | maxSize: 40% 27 | - path: / 28 | maxSize: 25% 29 | type: js 30 | - path: / 31 | maxSize: 40% 32 | ``` 33 | 34 | This default threshold configuration is meant to allow a create-react-app generated application to pass. Your application may vary and you may need to configure your thresholds to sensible limits. These also may seem low but this is only testing the index page. Routes may be added at a later date that will help here. 35 | -------------------------------------------------------------------------------- /packages/gimbal/docs/output/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Output 2 | 3 | After Gimbal executes a command, it has to output the report the command generated. Gimbal will output a report to the CLI and can optionally generate other reports on disk: 4 | 5 | - [HTML](./html) 6 | - [JSON](./json) 7 | - [Markdown](./markdown) 8 | -------------------------------------------------------------------------------- /packages/gimbal/docs/output/html/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal HTML Report 2 | 3 | Gimbal can output simple tables as a report. If a command executed with multiple modules, each module will be its own table and each table will have a label, the value, the threshold and a success column. 4 | 5 | ## Usage 6 | 7 | You can tell Gimbal to write this file out using the configuration file: 8 | 9 | ```yaml 10 | outputs: 11 | html: ./artifacts/gimbal.html 12 | ``` 13 | 14 | Or via cli: 15 | 16 | ```shell 17 | $ gimbal --output-html ./artifacts/gimbal.html 18 | ``` 19 | 20 | These paths are relative to where gimbal is being executed or being told to execute. 21 | -------------------------------------------------------------------------------- /packages/gimbal/docs/output/json/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal JSON Report 2 | 3 | Gimbal can write the raw JSON blob of all the audits that were just run. If multiple audits were run, these will all be located in a single JSON file. 4 | 5 | ## Usage 6 | 7 | You can tell Gimbal to write this file out using the configuration file: 8 | 9 | ```yaml 10 | outputs: 11 | json: ./artifacts/gimbal.json 12 | ``` 13 | 14 | Or via cli: 15 | 16 | ```shell 17 | $ gimbal --output-json ./artifacts/gimbal.json 18 | ``` 19 | 20 | These paths are relative to where gimbal is being executed or being told to execute. 21 | -------------------------------------------------------------------------------- /packages/gimbal/docs/output/markdown/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Markdown Report 2 | 3 | Gimbal can output simple tables as a report. If a command executed with multiple modules, each module will be its own table and each table will have a label, the value, the threshold and a success column. 4 | 5 | ## Usage 6 | 7 | You can tell Gimbal to write this file out using the configuration file: 8 | 9 | ```yaml 10 | outputs: 11 | markdown: ./artifacts/gimbal.md 12 | ``` 13 | 14 | Or via cli: 15 | 16 | ```shell 17 | $ gimbal --output-markdown ./artifacts/gimbal.md 18 | ``` 19 | 20 | These paths are relative to where gimbal is being executed or being told to execute. 21 | -------------------------------------------------------------------------------- /packages/gimbal/docs/utils/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal Utilities 2 | 3 | There are utilities that do not fit in any one command or module. These are abstracted away as utilities. 4 | 5 | TODO 6 | -------------------------------------------------------------------------------- /packages/gimbal/docs/vcs/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal VCS Integrations 2 | 3 | Projects may be stored in a [Version Control System](https://en.wikipedia.org/wiki/Version_control) (VCS) and Gimbal has certain integrations to enable features for these individual systems: 4 | 5 | - [GitHub](./github) 6 | -------------------------------------------------------------------------------- /packages/gimbal/docs/vcs/github/README.md: -------------------------------------------------------------------------------- 1 | # Gimbal GitHub Integration 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /packages/gimbal/envs/CircleCI/commit.env: -------------------------------------------------------------------------------- 1 | # https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables 2 | BASH_ENV=/tmp/.bash_env-5ccb028a0d55260008224595-0-build 3 | CI=true 4 | CIRCLECI=true 5 | CIRCLE_BRANCH=default-cra 6 | CIRCLE_BUILD_NUM=627 7 | CIRCLE_BUILD_URL=https://circleci.com/gh/ModusCreateOrg/gimbal/627 8 | CIRCLE_COMPARE_URL= 9 | CIRCLE_JOB=sample-test 10 | CIRCLE_NODE_INDEX=0 11 | CIRCLE_NODE_TOTAL=1 12 | CIRCLE_PREVIOUS_BUILD_NUM=626 13 | CIRCLE_PROJECT_REPONAME=gimbal 14 | CIRCLE_PROJECT_USERNAME=ModusCreateOrg 15 | CIRCLE_REPOSITORY_URL=git@github.com:ModusCreateOrg/gimbal.git 16 | CIRCLE_SHA1=2caf7cba409670b85efeccac0514d2eb85b1fdd2 17 | CIRCLE_SHELL_ENV=/tmp/.bash_env-5ccb028a0d55260008224595-0-build 18 | CIRCLE_STAGE=sample-test 19 | CIRCLE_USERNAME=mitchellsimoens 20 | CIRCLE_WORKFLOW_ID=d7ea9a21-4d51-4c5f-b211-63d319555f62 21 | CIRCLE_WORKFLOW_JOB_ID=4ab95001-84ac-4fac-89e8-a428dbc7b520 22 | CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS= 23 | CIRCLE_WORKFLOW_WORKSPACE_ID=d7ea9a21-4d51-4c5f-b211-63d319555f62 24 | CIRCLE_WORKING_DIRECTORY=~/repo 25 | -------------------------------------------------------------------------------- /packages/gimbal/envs/CircleCI/pr.env: -------------------------------------------------------------------------------- 1 | # https://circleci.com/docs/2.0/env-vars/#built-in-environment-variables 2 | CI=true 3 | CIRCLE_BRANCH=my-feature/branch 4 | CIRCLE_BUILD_NUM=6342 5 | CIRCLE_BUILD_URL=https://circleci.com/gh/ModusCreateOrg/gimbal/6342 6 | CIRCLE_COMPARE_URL= 7 | CIRCLE_INTERNAL_TASK_DATA= 8 | CIRCLE_JOB=doing-something 9 | CIRCLE_NODE_INDEX=0 10 | CIRCLE_PR_NUMBER=72 11 | CIRCLE_PR_REPONAME=gimbal 12 | CIRCLE_PR_USERNAME=ModusCreateOrg 13 | CIRCLE_PREVIOUS_BUILD_NUM=6341 14 | CIRCLE_PROJECT_REPONAME=gimbal 15 | CIRCLE_PROJECT_USERNAME=ModusCreateOrg 16 | CIRCLE_PULL_REQUEST=https://github.com/ModusCreateOrg/gimbal/pull/72 17 | CIRCLE_PULL_REQUESTS=https://github.com/ModusCreateOrg/gimbal/pull/72 18 | CIRCLE_REPOSITORY_URL=git@github.com:ModusCreateOrg/gimbal.git 19 | CIRCLE_SHA1=183eb68cf9c139cbd220f22b2c2b5a5286a67863 20 | CIRCLE_TAG= 21 | CIRCLE_USERNAME=ModusCreateOrg 22 | CIRCLE_WORKFLOW_ID=abcdef 23 | CIRCLE_WORKING_DIRECTORY=~/repo 24 | CIRCLECI=true 25 | HOME=/root 26 | -------------------------------------------------------------------------------- /packages/gimbal/envs/TravisCI/commit.env: -------------------------------------------------------------------------------- 1 | # https://docs.travis-ci.com/user/environment-variables/#default-environment-variables 2 | CI=true 3 | CONTINUOUS_INTEGRATION=true 4 | TRAVIS_ALLOW_FAILURE=false 5 | TRAVIS_APP_HOST=build.travis-ci.com 6 | TRAVIS_APT_PROXY= 7 | TRAVIS_ARCH=amd64 8 | TRAVIS_BRANCH=travis_test 9 | TRAVIS_BUILD_DIR=/home/travis/build/ModusCreateOrg/gimbal 10 | TRAVIS_BUILD_ID=110405599 11 | TRAVIS_BUILD_NUMBER=5 12 | TRAVIS_BUILD_STAGE_NAME=Test 13 | TRAVIS_BUILD_WEB_URL=https://travis-ci.com/ModusCreateOrg/gimbal/builds/110405599 14 | TRAVIS_CMD=gimbal 15 | TRAVIS_COMMIT_MESSAGE=exeucte gimbal 16 | TRAVIS_COMMIT_RANGE=19a25a913564...47b4ab827bc1 17 | TRAVIS_COMMIT=47b4ab827bc11a75cd92cc3345f7fbd658dbb525 18 | TRAVIS_DIST=xenial 19 | TRAVIS_ENABLE_INFRA_DETECTION=true 20 | TRAVIS_EVENT_TYPE=push 21 | TRAVIS_HOME=/home/travis 22 | TRAVIS_INFRA=gce 23 | TRAVIS_INIT=systemd 24 | TRAVIS_INTERNAL_RUBY_REGEX=^ruby-(2\.[0-4]\.[0-9]|1\.9\.3) 25 | TRAVIS_JOB_ID=197228702 26 | TRAVIS_JOB_NAME=Execute Gimbal 27 | TRAVIS_JOB_NUMBER=5.1 28 | TRAVIS_JOB_WEB_URL=https://travis-ci.com/ModusCreateOrg/gimbal/jobs/197228702 29 | TRAVIS_LANGUAGE=node_js 30 | TRAVIS_NODE_VERSION=10.15.3 31 | TRAVIS_OSX_IMAGE= 32 | TRAVIS_OS_NAME=linux 33 | TRAVIS_PRE_CHEF_BOOTSTRAP_TIME=2019-03-25T16:16:42 34 | TRAVIS_PULL_REQUEST_BRANCH= 35 | TRAVIS_PULL_REQUEST_SHA= 36 | TRAVIS_PULL_REQUEST_SLUG= 37 | TRAVIS_PULL_REQUEST=false 38 | TRAVIS_REPO_SLUG=ModusCreateOrg/gimbal 39 | TRAVIS_ROOT=/ 40 | TRAVIS_SECURE_ENV_VARS=false 41 | TRAVIS_STACK_FEATURES=basic couchdb disabled-ipv6 docker docker-compose elasticsearch firefox go-toolchain google-chrome jdk memcached mongodb mysql nodejs_interpreter perl_interpreter perlbrew phantomjs postgresql python_interpreter redis ruby_interpreter sqlite xserver 42 | TRAVIS_STACK_LANGUAGES=__sardonyx__ c c++ clojure cplusplus cpp default generic go groovy java node_js php pure_java python ruby scala 43 | TRAVIS_STACK_JOB_BOARD_REGISTER=/.job-board-register.yml 44 | TRAVIS_STACK_NAME=sardonyx 45 | TRAVIS_STACK_NODE_ATTRIBUTES=/.node-attributes.yml 46 | TRAVIS_STACK_TIMESTAMP=2019-03-25 16:16:56 UTC 47 | TRAVIS_SUDO=true 48 | TRAVIS_TAG= 49 | TRAVIS_TEST_RESULT= 50 | TRAVIS_TIMER_ID=04d20830 51 | TRAVIS_TIMER_START_TIME=1556813224818183780 52 | TRAVIS_TMPDIR=/tmp/tmp.I5wlPMYfMO 53 | TRAVIS_UID=2000 54 | TRAVIS=true 55 | -------------------------------------------------------------------------------- /packages/gimbal/jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest/utils'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | module.exports = { 5 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 6 | prefix: '/', 7 | }), 8 | roots: ['/src'], 9 | transform: { 10 | '^.+\\.tsx?$': 'ts-jest', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/gimbal/src/README.md: -------------------------------------------------------------------------------- 1 | # src 2 | 3 | ## Directory Structure 4 | 5 | ### `/src/command` 6 | 7 | These are the commands that are used in `/src/index.ts` and are the entry points into this tool. 8 | 9 | ### `/src/module` 10 | 11 | These are meant to be the small pieces of actionable units that other modules or commands execute. 12 | 13 | ### `/src/utils` 14 | 15 | A collection of utilitarian functions and classes. 16 | -------------------------------------------------------------------------------- /packages/gimbal/src/ascii_art/gimbal.txt: -------------------------------------------------------------------------------- 1 | ___ ___ ___ 2 | /\__\ /\ \ _____ /\ \ 3 | /:/ _/_ ___ |::\ \ /::\ \ /::\ \ 4 | /:/ /\ \ /\__\ |:|:\ \ /:/\:\ \ /:/\:\ \ 5 | /:/ /::\ \ /:/__/ __|:|\:\ \ /:/ /::\__\ /:/ /::\ \ ___ ___ 6 | /:/__\/\:\__\ /::\ \ /::::|_\:\__\ /:/_/:/\:|__| /:/_/:/\:\__\ /\ \ /\__\ 7 | \:\ \ /:/ / \/\:\ \__ \:\~~\ \/__/ \:\/:/ /:/ / \:\/:/ \/__/ \:\ \ /:/ / 8 | \:\ /:/ / \:\/\__\ \:\ \ \::/_/:/ / \::/__/ \:\ /:/ / 9 | \:\/:/ / \::/ / \:\ \ \:\/:/ / \:\ \ \:\/:/ / 10 | \::/ / /:/ / \:\__\ \::/ / \:\__\ \::/ / 11 | \/__/ \/__/ \/__/ \/__/ \/__/ \/__/ 12 | 13 | _ __ __ _ ____ _ 14 | | |__ _ _ | \/ | ___ __| | _ _ ___ / ___| _ __ ___ __ _ | |_ ___ 15 | | '_ \ | | | | | |\/| | / _ \ / _` | | | | | / __| | | | '__| / _ \ / _` | | __| / _ \ 16 | | |_) | | |_| | | | | | | (_) | | (_| | | |_| | \__ \ | |___ | | | __/ | (_| | | |_ | __/ 17 | |_.__/ \__, | |_| |_| \___/ \__,_| \__,_| |___/ \____| |_| \___| \__,_| \__| \___| 18 | |___/ 19 | ───────────────────────────────────────────────────────────────────────────────────────────────────── 20 | -------------------------------------------------------------------------------- /packages/gimbal/src/ci/CircleCI/index.ts: -------------------------------------------------------------------------------- 1 | import env from '@modus/gimbal-core/lib/utils/env'; 2 | import { CIMode } from '@/typings/ci'; 3 | import { VCS as VCSTypes } from '@/typings/vcs'; 4 | import whichVCS from '@/vcs'; 5 | 6 | export default class CircleCI { 7 | private $vcs?: VCSTypes; 8 | 9 | public static is(): boolean { 10 | return env('CIRCLECI', false) as boolean; 11 | } 12 | 13 | public get mode(): CIMode { 14 | return env('CIRCLE_PULL_REQUEST') ? 'pr' : 'commit'; 15 | } 16 | 17 | public get name(): string { 18 | return this.constructor.name; 19 | } 20 | 21 | public get owner(): string { 22 | return env('CIRCLE_PROJECT_USERNAME'); 23 | } 24 | 25 | public get pr(): number | void { 26 | const pr = env('CIRCLE_PR_NUMBER'); 27 | 28 | if (pr) { 29 | return pr as number; 30 | } 31 | 32 | const url = env('CIRCLE_PULL_REQUEST'); 33 | 34 | if (url) { 35 | const num = url.split('/').pop(); 36 | 37 | if (num) { 38 | return num as number; 39 | } 40 | } 41 | 42 | return undefined; 43 | } 44 | 45 | public get repo(): string { 46 | return env('CIRCLE_PROJECT_REPONAME'); 47 | } 48 | 49 | public get sha(): string { 50 | return env('CIRCLE_SHA1') as string; 51 | } 52 | 53 | public get vcs(): VCSTypes | void { 54 | if (this.$vcs) { 55 | return this.$vcs; 56 | } 57 | 58 | const repoUrl = env('CIRCLE_REPOSITORY_URL'); 59 | 60 | if (repoUrl) { 61 | this.$vcs = whichVCS(repoUrl) as VCSTypes; 62 | 63 | this.$vcs.ci = this; 64 | 65 | return this.$vcs; 66 | } 67 | 68 | return undefined; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/gimbal/src/ci/GitHubActions/index.ts: -------------------------------------------------------------------------------- 1 | import env from '@modus/gimbal-core/lib/utils/env'; 2 | import { CIMode } from '@/typings/ci'; 3 | import { VCS as VCSTypes } from '@/typings/vcs'; 4 | import GitHub from '@/vcs/GitHub'; 5 | 6 | export default class GitHubActions { 7 | private $vcs?: VCSTypes; 8 | 9 | public static is(): boolean { 10 | return env('GITHUB_ACTIONS', false) as boolean; 11 | } 12 | 13 | public get mode(): CIMode { 14 | return 'commit'; 15 | } 16 | 17 | public get name(): string { 18 | return this.constructor.name; 19 | } 20 | 21 | public get owner(): string { 22 | return env('GITHUB_REPOSITORY').split('/')[0]; 23 | } 24 | 25 | public get pr(): number | void { 26 | return undefined; 27 | } 28 | 29 | public get repo(): string { 30 | return env('GITHUB_REPOSITORY').split('/')[1]; 31 | } 32 | 33 | public get sha(): string { 34 | return env('GITHUB_SHA') as string; 35 | } 36 | 37 | public get vcs(): GitHub | void { 38 | if (this.$vcs) { 39 | return this.$vcs; 40 | } 41 | 42 | this.$vcs = new GitHub(); 43 | 44 | this.$vcs.ci = this; 45 | 46 | return this.$vcs; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/gimbal/src/ci/TravisCI/index.ts: -------------------------------------------------------------------------------- 1 | import env from '@modus/gimbal-core/lib/utils/env'; 2 | import { CIMode } from '@/typings/ci'; 3 | import { VCS as VCSTypes } from '@/typings/vcs'; 4 | import GitHub from '@/vcs/GitHub'; 5 | 6 | export default class TravisCI { 7 | private $vcs?: VCSTypes; 8 | 9 | private get slugSplit(): string[] { 10 | const slug = env('TRAVIS_REPO_SLUG'); 11 | 12 | return slug.split('/'); 13 | } 14 | 15 | public static is(): boolean { 16 | return env('TRAVIS', false) as boolean; 17 | } 18 | 19 | public get mode(): CIMode { 20 | return env('TRAVIS_PULL_REQUEST') ? 'pr' : 'commit'; 21 | } 22 | 23 | public get name(): string { 24 | return this.constructor.name; 25 | } 26 | 27 | public get owner(): string { 28 | return this.slugSplit[0]; 29 | } 30 | 31 | public get pr(): number | void { 32 | const pr = env('TRAVIS_PULL_REQUEST'); 33 | 34 | return pr ? (pr as number) : undefined; 35 | } 36 | 37 | public get repo(): string { 38 | return this.slugSplit[1]; 39 | } 40 | 41 | public get sha(): string { 42 | return env('TRAVIS_COMMIT') as string; 43 | } 44 | 45 | public get vcs(): GitHub | void { 46 | if (this.$vcs) { 47 | return this.$vcs; 48 | } 49 | 50 | const slug = env('TRAVIS_REPO_SLUG'); 51 | 52 | if (slug) { 53 | this.$vcs = new GitHub(); 54 | 55 | this.$vcs.ci = this; 56 | 57 | return this.$vcs; 58 | } 59 | 60 | return undefined; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /packages/gimbal/src/ci/index.ts: -------------------------------------------------------------------------------- 1 | import Config from '@/config'; 2 | import { Cls } from '@/typings/ci'; 3 | import CircleCICls from './CircleCI'; 4 | import GitHubActionsCls from './GitHubActions'; 5 | import TravisCICls from './TravisCI'; 6 | 7 | export const CircleCI = 'CircleCI'; 8 | export const GitHubActions = 'GitHubActions'; 9 | export const TravisCI = 'TravisCI'; 10 | 11 | export type CIs = CircleCICls | GitHubActionsCls | TravisCICls; 12 | 13 | let ci: CIs | void; 14 | 15 | interface Tests { 16 | [label: string]: Cls; 17 | } 18 | 19 | interface CIConfig { 20 | provider: string; 21 | } 22 | 23 | const tests: Tests = { 24 | [CircleCI]: CircleCICls, 25 | [GitHubActions]: GitHubActionsCls, 26 | [TravisCI]: TravisCICls, 27 | }; 28 | 29 | const normalizeConfiguredCI = (configuredCI?: string | CIConfig): CIConfig | void => { 30 | if (configuredCI) { 31 | return typeof configuredCI === 'string' ? { provider: configuredCI } : configuredCI; 32 | } 33 | 34 | return undefined; 35 | }; 36 | 37 | const whichCI = (): CIs | void => { 38 | if (ci) { 39 | return ci; 40 | } 41 | 42 | const configuredCI = normalizeConfiguredCI(Config.get('configs.ci')); 43 | const CI = configuredCI ? configuredCI.provider : Object.keys(tests).find((key: string): boolean => tests[key].is()); 44 | 45 | switch (CI) { 46 | case CircleCI: 47 | ci = new CircleCICls(); 48 | 49 | return ci; 50 | case GitHubActions: 51 | ci = new GitHubActionsCls(); 52 | 53 | return ci; 54 | case TravisCI: 55 | ci = new TravisCICls(); 56 | 57 | return ci; 58 | default: 59 | return undefined; 60 | } 61 | }; 62 | 63 | export default whichCI; 64 | -------------------------------------------------------------------------------- /packages/gimbal/src/command/audit/program.ts: -------------------------------------------------------------------------------- 1 | import action from './index'; 2 | import Command from '@/command'; 3 | 4 | // eslint-disable-next-line no-new 5 | export default new Command({ 6 | action, 7 | command: 'audit', 8 | }); 9 | -------------------------------------------------------------------------------- /packages/gimbal/src/command/reconcile.ts: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge'; 2 | import reconcile from '@/module/reconcile'; 3 | import { Report, ReportItem } from '@/typings/command'; 4 | 5 | const findMatches = (item: ReportItem, type: string, rest: Report[]): ReportItem[] => { 6 | const matches: ReportItem[] = [item]; 7 | 8 | rest.forEach((report: Report): void => { 9 | if (report.data) { 10 | report.data.forEach((reportItem: ReportItem): void => { 11 | if (reportItem.data && reportItem.type === type) { 12 | reportItem.data.forEach((reportDataItem: ReportItem): void => { 13 | // need a better way as these may be the same on different things 14 | // unused source root path (http://localhost:3000/) may have different 15 | // types or be the page total. This would work if the threshold is 16 | // different but could be the same... 17 | if (reportDataItem.rawLabel === item.rawLabel && reportDataItem.rawThreshold === item.rawThreshold) { 18 | matches.push(reportDataItem); 19 | } 20 | }); 21 | } 22 | }); 23 | } 24 | }); 25 | 26 | return matches; 27 | }; 28 | 29 | const reconcileReports = (reports: Report | Report[]): Report => { 30 | if (Array.isArray(reports)) { 31 | const [first, ...rest] = reports; 32 | const cloned: Report = deepmerge(first, {}); 33 | 34 | // clone the reports prior to reconciling so that they are unchanged 35 | first.rawReports = reports; 36 | 37 | if (cloned.data) { 38 | cloned.data.forEach((report: ReportItem): void => { 39 | if (report.data) { 40 | // .map() creates a new array instance, make sure to poke it onto the report 41 | /* eslint-disable-next-line no-param-reassign */ 42 | report.data = report.data.map( 43 | (item: ReportItem): ReportItem => { 44 | const matches = findMatches(item, report.type, rest); 45 | 46 | return reconcile(matches, report.type); 47 | }, 48 | ); 49 | 50 | /* eslint-disable-next-line no-param-reassign */ 51 | report.success = report.data.every((item: ReportItem): boolean => item.success); 52 | } 53 | }); 54 | 55 | cloned.success = cloned.data.every((item: ReportItem): boolean => item.success); 56 | } 57 | 58 | return cloned; 59 | } 60 | 61 | return reports; 62 | }; 63 | 64 | export default reconcileReports; 65 | -------------------------------------------------------------------------------- /packages/gimbal/src/config/audits.ts: -------------------------------------------------------------------------------- 1 | import program, { Command } from 'commander'; 2 | import audit from '@/command/audit/program'; 3 | 4 | const processAudits = (): Promise => 5 | audit.run(program.commands.find((cmd: Command): boolean => cmd.name() === 'audit')); 6 | 7 | export default processAudits; 8 | -------------------------------------------------------------------------------- /packages/gimbal/src/config/loader/js.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@/typings/config'; 2 | 3 | const jsLoader = async (file: string): Promise => { 4 | const config = await import(file); 5 | 6 | if (typeof config === 'function') { 7 | return config(); 8 | } 9 | 10 | return config; 11 | }; 12 | 13 | export default jsLoader; 14 | -------------------------------------------------------------------------------- /packages/gimbal/src/config/loader/yaml.ts: -------------------------------------------------------------------------------- 1 | import env from '@modus/gimbal-core/lib/utils/env'; 2 | import { readFile } from '@modus/gimbal-core/lib/utils/fs'; 3 | import yaml from 'js-yaml'; 4 | import { Config } from '@/typings/config'; 5 | 6 | const VAR_RE = /\${(.+?):(.+?)(?:,\s*(.+?))?(?:,\s*(.+?))?}/gm; 7 | 8 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 9 | const parseValue = (value: any): any => { 10 | if (value === 'true') { 11 | return true; 12 | } 13 | 14 | if (value === 'false') { 15 | return false; 16 | } 17 | 18 | return value; 19 | }; 20 | 21 | const yamlLoader = async (file: string): Promise => { 22 | const source = await readFile(file, 'utf8'); 23 | // this will allow for variable replacement. The following are the supported types and formats: 24 | // - ${env:SOME_VAR} 25 | // The type is "env" which will lookup "SOME_VAR" on process.env. If the var is not found 26 | // then the text will not be replaced. 27 | // If the type is unrecognized, the text will not be replaced. 28 | const doc = source.replace( 29 | VAR_RE, 30 | (text: string, type: string, prop: string, trueValue?: string, falseValue?: string): string => { 31 | if (type === 'env') { 32 | const ret = env(prop); 33 | 34 | if (ret) { 35 | if (trueValue) { 36 | return parseValue(trueValue); 37 | } 38 | } else if (falseValue) { 39 | return parseValue(falseValue); 40 | } 41 | 42 | return ret === undefined ? text : ret; 43 | } 44 | 45 | return text; 46 | }, 47 | ); 48 | 49 | return yaml.safeLoad(doc, { 50 | filename: file, 51 | json: true, 52 | }); 53 | }; 54 | 55 | export default yamlLoader; 56 | -------------------------------------------------------------------------------- /packages/gimbal/src/config/resolver/index.ts: -------------------------------------------------------------------------------- 1 | import { resolvePath } from '@modus/gimbal-core/lib/utils/fs'; 2 | import { existsSync } from 'fs'; 3 | import path from 'path'; 4 | 5 | const IS_FILE_RE = /^[./]/; 6 | const IS_GIMBAL_RE = /^@\//; 7 | const IS_USER_HOME_RE = /^~/; 8 | 9 | const GIMBAL_ROOT = resolvePath(__dirname, '../..'); 10 | 11 | const resolver = (requested: string, dir: string, dirPrefix: string): string => { 12 | // if path starts with `~` then this is a filesystem path 13 | // that needs to be mapped to the user's home dir. This 14 | // should be handled by nodejs in a platform agnostic 15 | // way so we shouldn't need to do anything. 16 | if (requested.match(IS_USER_HOME_RE)) { 17 | return requested; 18 | } 19 | 20 | // if a path starts with `/` or `.` then this is a 21 | // filesystem path. 22 | if (requested.match(IS_FILE_RE)) { 23 | return resolvePath(dir, requested); 24 | } 25 | 26 | // if a path starts with '@/' then it should be looked up 27 | // as a gimbal internal plugin 28 | if (requested.match(IS_GIMBAL_RE)) { 29 | return resolvePath(GIMBAL_ROOT, requested.substr(2)); 30 | } 31 | 32 | const gimbalDir = path.join(GIMBAL_ROOT, dirPrefix, requested); 33 | 34 | if (existsSync(gimbalDir)) { 35 | return resolvePath(gimbalDir); 36 | } 37 | 38 | // see if it's a node module install on the cwd 39 | // where gimbal is running on 40 | const cwdRelativeNodeModule = path.join(dir, 'node_modules', requested); 41 | 42 | return existsSync(cwdRelativeNodeModule) ? cwdRelativeNodeModule : requested; 43 | }; 44 | 45 | export default resolver; 46 | -------------------------------------------------------------------------------- /packages/gimbal/src/event/Event.spec.ts: -------------------------------------------------------------------------------- 1 | import Event from './Event'; 2 | 3 | describe('@modus/gimbal-core/event/Event', (): void => { 4 | describe('fire', (): void => { 5 | it('should fire an event', (): void => { 6 | const fn = jest.fn().mockReturnValue('returned'); 7 | const instance = new Event('foo', fn); 8 | 9 | const ret = instance.fire('foo', 'bar'); 10 | 11 | expect(ret).toBe('returned'); 12 | 13 | expect(fn).toHaveBeenCalledWith('foo', 'bar'); 14 | }); 15 | 16 | it('should fire an event when configured with an object', (): void => { 17 | const fn = jest.fn().mockReturnValue('returned'); 18 | const instance = new Event('foo', { 19 | fn, 20 | priority: -10, 21 | }); 22 | 23 | expect(instance.priority).toBe(-10); 24 | 25 | const ret = instance.fire('foo', 'bar'); 26 | 27 | expect(ret).toBe('returned'); 28 | 29 | expect(fn).toHaveBeenCalledWith('foo', 'bar'); 30 | }); 31 | }); 32 | 33 | describe('createCallback', (): void => { 34 | it('should create a function to fire with', (): void => { 35 | const fn = jest.fn().mockReturnValue('returned'); 36 | const instance = new Event('foo', { 37 | fn, 38 | }); 39 | 40 | const cb = instance.createCallback('foo'); 41 | const ret = cb('bar'); 42 | 43 | expect(ret).toBe('returned'); 44 | 45 | expect(fn).toHaveBeenCalledWith('foo', 'bar'); 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/gimbal/src/event/Event.ts: -------------------------------------------------------------------------------- 1 | import { Callback, CreatedCallback, Config } from '@/typings/event/Event'; 2 | import { Data } from '@/typings/utils/Queue'; 3 | 4 | class Event { 5 | private event: string; 6 | 7 | private fn: Callback; 8 | 9 | public priority = 0; 10 | 11 | public constructor(event: string, config: Callback | Config) { 12 | this.event = event; 13 | 14 | if (typeof config === 'function') { 15 | this.fn = config; 16 | } else { 17 | this.fn = config.fn; 18 | 19 | if (config.priority) { 20 | this.priority = config.priority; 21 | } 22 | } 23 | } 24 | 25 | public fire(event: string, data: Data): Data | void { 26 | return this.fn(event, data); 27 | } 28 | 29 | public createCallback(event: string): CreatedCallback { 30 | return this.fire.bind(this, event); 31 | } 32 | } 33 | 34 | export default Event; 35 | -------------------------------------------------------------------------------- /packages/gimbal/src/event/index.ts: -------------------------------------------------------------------------------- 1 | import Queue from '@modus/gimbal-core/lib/utils/Queue'; 2 | import minimatch from 'minimatch'; 3 | import { FireRet } from '@/typings/event'; 4 | import { Callback, CreatedCallback, Config } from '@/typings/event/Event'; 5 | import { Data } from '@/typings/utils/Queue'; 6 | import Event from './Event'; 7 | 8 | interface EventMap { 9 | [name: string]: Event[]; 10 | } 11 | 12 | class Emitter { 13 | private events: EventMap = {}; 14 | 15 | public on(event: string, config: Callback | Config): Event { 16 | const { events } = this; 17 | 18 | if (!events[event]) { 19 | events[event] = []; 20 | } 21 | 22 | const instance = new Event(event, config); 23 | 24 | events[event].push(instance); 25 | 26 | return instance; 27 | } 28 | 29 | public un(event: string, instance: Event): void { 30 | const { events } = this; 31 | const { [event]: arr } = events; 32 | const index = arr && arr.indexOf(instance); 33 | 34 | if (arr && index !== -1) { 35 | arr.splice(index, 1); 36 | } 37 | } 38 | 39 | public async fire(event: string, data: Data): Promise { 40 | const { events } = this; 41 | const matched: Event[] = []; 42 | const ret: FireRet = { 43 | data, 44 | rets: [], 45 | }; 46 | 47 | Object.keys(events).forEach((name: string): void => { 48 | const match = minimatch(event, name); 49 | 50 | if (match) { 51 | matched.push(...events[name]); 52 | } 53 | }); 54 | 55 | if (matched.length > 0) { 56 | const queue = new Queue(); 57 | 58 | matched.sort((last: Event, next: Event): number => { 59 | const lastPriority = last.priority || 0; 60 | const nextPriority = next.priority || 0; 61 | 62 | if (lastPriority < nextPriority) { 63 | return -1; 64 | } 65 | 66 | if (lastPriority > nextPriority) { 67 | return 1; 68 | } 69 | 70 | return 0; 71 | }); 72 | 73 | const args = matched.map((eventInstance: Event): CreatedCallback => eventInstance.createCallback(event)); 74 | 75 | queue.add(...args); 76 | 77 | const rets = await queue.run(data); 78 | 79 | ret.rets.push(...rets); 80 | } 81 | 82 | return ret; 83 | } 84 | } 85 | 86 | export { Event }; 87 | 88 | export default new Emitter(); 89 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/chrome/default-config.ts: -------------------------------------------------------------------------------- 1 | import { LaunchOptions } from 'puppeteer'; 2 | 3 | // can accept anything from: https://github.com/GoogleChrome/puppeteer/blob/v1.14.0/docs/api.md#puppeteerlaunchoptions 4 | const defaultConfig: LaunchOptions = { 5 | // args useful for headless CI instances 6 | args: ['--no-sandbox', '–-disable-setuid-sandbox'], 7 | }; 8 | 9 | export default defaultConfig; 10 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/heap-snapshot/default-config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@/typings/module/heap-snapshot'; 2 | 3 | const defaultConfig: Config = { 4 | threshold: { 5 | Documents: 5, 6 | Frames: 2, 7 | LayoutCount: 5, 8 | Nodes: 75, 9 | RecalcStyleCount: 6, 10 | }, 11 | }; 12 | 13 | export default defaultConfig; 14 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/heap-snapshot/index.ts: -------------------------------------------------------------------------------- 1 | import { Metrics, Page } from 'puppeteer'; 2 | import Config from '@/config'; 3 | import EventEmitter from '@/event'; 4 | import { Report } from '@/typings/command'; 5 | import { 6 | Config as HeapSnapshotConfig, 7 | HeapMetrics, 8 | AuditStartEvent, 9 | AuditEndEvent, 10 | NavigationStartEvent, 11 | NavigationEndEvent, 12 | ReportStartEvent, 13 | ReportEndEvent, 14 | } from '@/typings/module/heap-snapshot'; 15 | import { CommandOptions } from '@/typings/utils/command'; 16 | import defaultConfig from './default-config'; 17 | import parseReport from './output'; 18 | 19 | const heapSnapshot = async ( 20 | page: Page, 21 | url: string, 22 | options: CommandOptions, 23 | config: HeapSnapshotConfig = Config.get('configs.heap-snapshot', defaultConfig), 24 | ): Promise => { 25 | const navigationStartEvent: NavigationStartEvent = { 26 | config, 27 | page, 28 | options, 29 | url, 30 | }; 31 | 32 | await EventEmitter.fire(`module/heap-snapsnot/navigation/start`, navigationStartEvent); 33 | 34 | await page.goto(url); 35 | 36 | const navigationEndEvent: NavigationEndEvent = { 37 | config, 38 | page, 39 | options, 40 | url, 41 | }; 42 | 43 | await EventEmitter.fire(`module/heap-snapsnot/navigation/end`, navigationEndEvent); 44 | 45 | const auditStartEvent: AuditStartEvent = { 46 | config, 47 | page, 48 | options, 49 | url, 50 | }; 51 | 52 | await EventEmitter.fire(`module/heap-snapsnot/audit/start`, auditStartEvent); 53 | 54 | const audit: Metrics = await page.metrics(); 55 | 56 | const auditEndEvent: AuditEndEvent = { 57 | audit, 58 | config, 59 | page, 60 | options, 61 | url, 62 | }; 63 | 64 | await EventEmitter.fire(`module/heap-snapsnot/audit/end`, auditEndEvent); 65 | 66 | const reportStartEvent: ReportStartEvent = { 67 | audit, 68 | config, 69 | page, 70 | options, 71 | url, 72 | }; 73 | 74 | await EventEmitter.fire(`module/heap-snapsnot/report/start`, reportStartEvent); 75 | 76 | const report = parseReport(audit as HeapMetrics, config, options); 77 | 78 | const reportEndEvent: ReportEndEvent = { 79 | audit, 80 | config, 81 | page, 82 | options, 83 | report, 84 | url, 85 | }; 86 | 87 | await EventEmitter.fire(`module/heap-snapsnot/report/end`, reportEndEvent); 88 | 89 | return report; 90 | }; 91 | 92 | export default heapSnapshot; 93 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/heap-snapshot/meta.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from '@/typings/module'; 2 | 3 | const meta: Meta = { 4 | thresholdLimit: 'upper', 5 | thresholdTypes: { 6 | Documents: 'number', 7 | Frames: 'number', 8 | JSHeapTotalSize: 'size', 9 | JSHeapUsedSize: 'size', 10 | LayoutCount: 'number', 11 | Nodes: 'number', 12 | RecalcStyleCount: 'number', 13 | }, 14 | }; 15 | 16 | export default meta; 17 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/heap-snapshot/output.ts: -------------------------------------------------------------------------------- 1 | import checkThreshold from '@modus/gimbal-core/lib/utils/threshold'; 2 | import { Report, ReportItem } from '@/typings/command'; 3 | import { Config as HeapSnapshotConfig, HeapMetrics } from '@/typings/module/heap-snapshot'; 4 | import { CommandOptions } from '@/typings/utils/command'; 5 | 6 | const keysToCareAbout = [ 7 | 'Documents', 8 | 'Frames', 9 | 'JSHeapTotalSize', 10 | 'JSHeapUsedSize', 11 | 'LayoutCount', 12 | 'Nodes', 13 | 'RecalcStyleCount', 14 | ]; 15 | 16 | const type = 'heap-snapshot'; 17 | 18 | const parseReport = (raw: HeapMetrics, { threshold }: HeapSnapshotConfig, options: CommandOptions): Report => { 19 | const { checkThresholds } = options; 20 | let success = true; 21 | 22 | const data: ReportItem[] = keysToCareAbout.map( 23 | (label: string): ReportItem => { 24 | const objThreshold = threshold[label]; 25 | const value = raw[label]; 26 | const objSuccess = !checkThresholds || objThreshold == null || checkThreshold(value, objThreshold); 27 | 28 | if (!objSuccess) { 29 | success = false; 30 | } 31 | 32 | return { 33 | label, 34 | rawLabel: label, 35 | rawThreshold: objThreshold, 36 | rawValue: value, 37 | threshold: objThreshold, 38 | thresholdLimit: 'lower', 39 | success: objSuccess, 40 | value, 41 | type, 42 | }; 43 | }, 44 | ); 45 | 46 | return { 47 | data: [ 48 | { 49 | data, 50 | label: 'Heap Snapshot Checks', 51 | rawLabel: 'Heap Snapshot Checks', 52 | success, 53 | type, 54 | }, 55 | ], 56 | raw, 57 | success, 58 | }; 59 | }; 60 | 61 | export default parseReport; 62 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/heap-snapshot/reconcile.ts: -------------------------------------------------------------------------------- 1 | import checkThreshold from '@modus/gimbal-core/lib/utils/threshold'; 2 | import { ReportItem } from '@/typings/command'; 3 | 4 | const reconcile = (matches: ReportItem[]): ReportItem => { 5 | const [first] = matches; 6 | 7 | if (matches.length > 1) { 8 | const item: ReportItem = { 9 | ...first, 10 | }; 11 | const number = matches.reduce((last: number, match: ReportItem): number => (match.rawValue as number) + last, 0); 12 | const value = number / matches.length; // get average 13 | 14 | item.rawValue = value; 15 | item.value = value.toFixed(0); 16 | item.success = item.rawThreshold == null || checkThreshold(value, item.rawThreshold); 17 | 18 | return item; 19 | } 20 | 21 | return first; 22 | }; 23 | 24 | export default reconcile; 25 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/heap-snapshot/register.ts: -------------------------------------------------------------------------------- 1 | import HeapSnapshot from '@/module/heap-snapshot'; 2 | import { register } from '@/module/registry'; 3 | import { Report } from '@/typings/command'; 4 | import { Options } from '@/typings/module/registry'; 5 | import meta from './meta'; 6 | 7 | register( 8 | 'heap-snapshot', 9 | meta, 10 | async ({ chrome, commandOptions, url }: Options): Promise => { 11 | const page = await chrome.newPage(); 12 | 13 | if (page) { 14 | const report = await HeapSnapshot(page, url, commandOptions); 15 | 16 | await page.close(); 17 | 18 | return report; 19 | } 20 | 21 | throw new Error('Could not open page to get heap snapshot'); 22 | }, 23 | ); 24 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/lighthouse/default-config.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@/typings/module/lighthouse'; 2 | 3 | const defaultConfig: Config = { 4 | extends: 'lighthouse:default', 5 | settings: { 6 | skipAudits: ['uses-http2', 'redirects-http', 'uses-long-cache-ttl'], 7 | }, 8 | threshold: { 9 | accessibility: 75, 10 | 'best-practices': 95, 11 | performance: 50, 12 | pwa: 50, 13 | seo: 90, 14 | }, 15 | }; 16 | 17 | export default defaultConfig; 18 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/lighthouse/meta.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from '@/typings/module'; 2 | 3 | const meta: Meta = { 4 | thresholdLimit: 'lower', 5 | thresholdTypes: { 6 | Accessibility: 'number', 7 | 'Best Practices': 'number', 8 | Performance: 'number', 9 | pwa: 'number', 10 | seo: 'number', 11 | }, 12 | }; 13 | 14 | export default meta; 15 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/lighthouse/output.ts: -------------------------------------------------------------------------------- 1 | import { mkdirp, resolvePath, writeFile } from '@modus/gimbal-core/lib/utils/fs'; 2 | import checkThreshold from '@modus/gimbal-core/lib/utils/threshold'; 3 | import { dirname } from 'path'; 4 | import { Report, ReportItem } from '@/typings/command'; 5 | import { Audit, Config } from '@/typings/module/lighthouse'; 6 | import { CommandOptions } from '@/typings/utils/command'; 7 | import { AdvancedThreshold } from '@/typings/utils/threshold'; 8 | 9 | const type = 'lighthouse'; 10 | 11 | const parseReport = async (raw: Audit, { outputHtml, threshold }: Config, options: CommandOptions): Promise => { 12 | const { lhr, report } = raw; 13 | const { checkThresholds } = options; 14 | const isComplexThreshold = typeof threshold === 'object'; 15 | let success = true; 16 | 17 | const data: ReportItem[] = Object.keys(lhr.categories).map( 18 | (label: string): ReportItem => { 19 | const obj = lhr.categories[label]; 20 | const value = obj.score * 100; 21 | const thresholdNumber: number = (isComplexThreshold 22 | ? (threshold as AdvancedThreshold)[label] 23 | : threshold) as number; 24 | const objSuccess = !checkThresholds || checkThreshold(value, thresholdNumber, 'lower'); 25 | 26 | if (!objSuccess) { 27 | success = objSuccess; 28 | } 29 | 30 | return { 31 | label: obj.title, 32 | rawLabel: label, 33 | rawThreshold: thresholdNumber, 34 | rawValue: obj.score, 35 | threshold: thresholdNumber, 36 | thresholdLimit: 'lower', 37 | success: objSuccess, 38 | value: value.toFixed(0), 39 | type, 40 | }; 41 | }, 42 | ); 43 | 44 | if (outputHtml && report) { 45 | const [, html] = report; 46 | const path = resolvePath(options.cwd, outputHtml); 47 | 48 | await mkdirp(dirname(path)); 49 | 50 | await writeFile(path, html, 'utf8'); 51 | } 52 | 53 | return { 54 | data: [ 55 | { 56 | data, 57 | label: 'Lighthouse Audits', 58 | rawLabel: 'Lighthouse Audits', 59 | success, 60 | type, 61 | }, 62 | ], 63 | raw, 64 | success, 65 | }; 66 | }; 67 | 68 | export default parseReport; 69 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/lighthouse/reconcile.ts: -------------------------------------------------------------------------------- 1 | import checkThreshold from '@modus/gimbal-core/lib/utils/threshold'; 2 | import { ReportItem } from '@/typings/command'; 3 | 4 | const reconcile = (matches: ReportItem[]): ReportItem => { 5 | const [first] = matches; 6 | 7 | if (matches.length > 1) { 8 | const item: ReportItem = { 9 | ...first, 10 | }; 11 | const totalScore = matches.reduce( 12 | (last: number, match: ReportItem): number => (match.rawValue as number) + last, 13 | 0, 14 | ); 15 | const averageScore = totalScore / matches.length; // get average 16 | const value = averageScore * 100; 17 | 18 | item.rawValue = averageScore; 19 | item.value = value.toFixed(0); 20 | item.success = item.rawThreshold == null || checkThreshold(value, item.rawThreshold as number, 'lower'); 21 | 22 | return item; 23 | } 24 | 25 | return first; 26 | }; 27 | 28 | export default reconcile; 29 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/lighthouse/register.ts: -------------------------------------------------------------------------------- 1 | import Lighthouse from '@/module/lighthouse'; 2 | import { register } from '@/module/registry'; 3 | import { Report } from '@/typings/command'; 4 | import { Options } from '@/typings/module/registry'; 5 | import meta from './meta'; 6 | 7 | register( 8 | 'lighthouse', 9 | meta, 10 | ({ chrome, commandOptions, url }: Options): Promise => 11 | Lighthouse( 12 | url, 13 | { 14 | chromePort: chrome.port as string, 15 | }, 16 | commandOptions, 17 | ), 18 | ); 19 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/reconcile.ts: -------------------------------------------------------------------------------- 1 | import { ReportItem } from '@/typings/command'; 2 | import HeapSnapshotReconcile from './heap-snapshot/reconcile'; 3 | import LighthouseReconcile from './lighthouse/reconcile'; 4 | import UnusedSourceReconcile from './unused-source/reconcile'; 5 | 6 | const reconcile = (matches: ReportItem[], type: string): ReportItem => { 7 | switch (type) { 8 | case 'heap-snapshot': 9 | return HeapSnapshotReconcile(matches); 10 | case 'lighthouse': 11 | return LighthouseReconcile(matches); 12 | case 'unused-source': 13 | return UnusedSourceReconcile(matches); 14 | default: 15 | // cannot reconcile 16 | // maybe we can do some default reconciliation? 17 | return matches[0]; 18 | } 19 | }; 20 | 21 | export default reconcile; 22 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/registry.ts: -------------------------------------------------------------------------------- 1 | import { ModuleInfo, Module } from '@/typings/module/registry'; 2 | import { Meta } from '@/typings/module'; 3 | 4 | const registry = new Map(); 5 | 6 | export const register = (name: string, meta: Meta, mod: Module): void => { 7 | registry.set(name, { 8 | fn: mod, 9 | meta, 10 | }); 11 | }; 12 | 13 | export const unregister = (name: string): boolean => registry.delete(name); 14 | 15 | export const get = (name: string): Module | void => { 16 | const info = registry.get(name); 17 | 18 | return info && info.fn; 19 | }; 20 | 21 | export const getMeta = (name: string): Meta | void => { 22 | const info = registry.get(name); 23 | 24 | return info && info.meta; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/size/default-config.ts: -------------------------------------------------------------------------------- 1 | import { resolvePath } from '@modus/gimbal-core/lib/utils/fs'; 2 | import { SizeConfig } from '@/typings/module/size'; 3 | import { CommandOptions } from '@/typings/utils/command'; 4 | 5 | const defaultConfig = ({ cwd, buildDir }: CommandOptions): SizeConfig => { 6 | const resolvedBuildDir = resolvePath(cwd, buildDir as string); 7 | 8 | return { 9 | threshold: [ 10 | { 11 | path: resolvePath(resolvedBuildDir, 'static/css/main.*.chunk.css'), 12 | maxSize: '5 KB', 13 | }, 14 | { 15 | path: resolvePath(resolvedBuildDir, 'static/js/main.*.chunk.js'), 16 | maxSize: '5 KB', 17 | }, 18 | { 19 | path: resolvePath(resolvedBuildDir, 'static/js/*.chunk.js'), 20 | maxSize: '150 KB', 21 | }, 22 | { 23 | path: resolvePath(resolvedBuildDir, 'static/js/runtime*.js'), 24 | maxSize: '5 KB', 25 | }, 26 | { 27 | path: resolvePath(resolvedBuildDir, 'static/media/logo*.svg'), 28 | maxSize: '3 KB', 29 | }, 30 | { 31 | path: resolvePath(resolvedBuildDir, 'favicon.ico'), 32 | maxSize: '4 KB', 33 | }, 34 | { 35 | path: resolvePath(resolvedBuildDir, 'index.html'), 36 | maxSize: '3 KB', 37 | }, 38 | { 39 | path: resolvePath(resolvedBuildDir, 'manifest.json'), 40 | maxSize: '500 B', 41 | }, 42 | { 43 | path: resolvePath(resolvedBuildDir, 'precache-*.js'), 44 | maxSize: '1 KB', 45 | }, 46 | { 47 | path: resolvePath(resolvedBuildDir, 'service-worker.js'), 48 | maxSize: '1.2 KB', 49 | }, 50 | { 51 | path: resolvedBuildDir, 52 | maxSize: '500 KB', 53 | }, 54 | ], 55 | }; 56 | }; 57 | 58 | export default defaultConfig; 59 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/size/meta.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from '@/typings/module'; 2 | 3 | const meta: Meta = { 4 | maxNumRoutes: 1, 5 | thresholdLimit: 'upper', 6 | thresholdType: 'size', 7 | }; 8 | 9 | export default meta; 10 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/size/output.ts: -------------------------------------------------------------------------------- 1 | import checkThreshold from '@modus/gimbal-core/lib/utils/threshold'; 2 | import bytes from 'bytes'; 3 | import { relative } from 'path'; 4 | import { Report, ReportItem } from '@/typings/command'; 5 | import { FileResult } from '@/typings/module/size'; 6 | import { CommandOptions } from '@/typings/utils/command'; 7 | 8 | const bytesConfig = { unitSeparator: ' ' }; 9 | const type = 'size'; 10 | 11 | interface ParseOptions { 12 | cwd: string; 13 | success: boolean; 14 | } 15 | 16 | const parseReport = (raw: FileResult[], options: CommandOptions): Report => { 17 | const { checkThresholds, cwd } = options; 18 | let success = true; 19 | 20 | const data = raw.map( 21 | (result: FileResult): ReportItem => { 22 | const resultSuccess = checkThresholds ? checkThreshold(result.sizeBytes, result.maxSizeBytes) : true; 23 | 24 | if (success) { 25 | success = resultSuccess; 26 | } 27 | 28 | return { 29 | label: relative(cwd, result.filePath), 30 | rawLabel: result.filePath, 31 | rawThreshold: result.maxSizeBytes, 32 | rawValue: result.size, 33 | success: resultSuccess, 34 | threshold: result.maxSize, 35 | thresholdLimit: 'upper', 36 | value: bytes(result.sizeBytes, bytesConfig), 37 | type, 38 | }; 39 | }, 40 | ); 41 | 42 | return { 43 | data: [ 44 | { 45 | data, 46 | label: 'Size Checks', 47 | rawLabel: 'Size Checks', 48 | success, 49 | type, 50 | }, 51 | ], 52 | raw, 53 | success, 54 | }; 55 | }; 56 | 57 | export default parseReport; 58 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/size/register.ts: -------------------------------------------------------------------------------- 1 | import { register } from '@/module/registry'; 2 | import Size from '@/module/size'; 3 | import { Report } from '@/typings/command'; 4 | import { Options } from '@/typings/module/registry'; 5 | import meta from './meta'; 6 | 7 | register('size', meta, ({ commandOptions }: Options): Promise => Size(commandOptions)); 8 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/unused-source/default-config.ts: -------------------------------------------------------------------------------- 1 | import { SizeConfigs } from '@/typings/module/size'; 2 | import { UnusedSourceConfig } from '@/typings/module/unused-source'; 3 | 4 | const defaultConfig: UnusedSourceConfig = { 5 | /** 6 | * A `threshold` value can be a number or string or an array of size thresholds: 7 | * 8 | * If a string, `n%` Will check based on unused percentage where n is a number like `30%`. 9 | */ 10 | threshold: [ 11 | { 12 | maxSize: '35%', 13 | path: '**/*/*.css', 14 | }, 15 | { 16 | maxSize: '15%', 17 | path: '**/*/main.*.js', 18 | }, 19 | { 20 | maxSize: '70%', 21 | path: '**/*.js', 22 | }, 23 | { 24 | maxSize: '30%', 25 | path: '/', 26 | type: 'js', 27 | }, 28 | { 29 | maxSize: '65%', 30 | path: '/', 31 | }, 32 | ] as SizeConfigs[], 33 | // Examples: 34 | 35 | // The array of objects, the path is relative to the base url. If the url is `http://localhost/foo/bar.js` 36 | // then `/foo/bar.js` will be used to find a matching threshold based on the `path` within the object: 37 | // threshold: [ 38 | // { 39 | // maxSize: '50%', 40 | // path: '**/*/*.css', 41 | // type: 'css', // optional 42 | // }, 43 | // { 44 | // maxSize: '50%', 45 | // path: '**/*/*.js', 46 | // type: 'js', // optional 47 | // }, 48 | // { 49 | // maxSize: '50%', 50 | // path: '/' 51 | // } 52 | // ], 53 | }; 54 | 55 | export default defaultConfig; 56 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/unused-source/meta.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from '@/typings/module'; 2 | 3 | const meta: Meta = { 4 | thresholdLimit: 'upper', 5 | thresholdType: 'percentage', 6 | }; 7 | 8 | export default meta; 9 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/unused-source/output.ts: -------------------------------------------------------------------------------- 1 | import { Report, ReportItem } from '@/typings/command'; 2 | import { Entry } from '@/typings/module/unused-source'; 3 | import { CommandOptions } from '@/typings/utils/command'; 4 | 5 | const type = 'unused-source'; 6 | 7 | const parseReport = (raw: Entry[], options: CommandOptions): Report => { 8 | const { checkThresholds } = options; 9 | const success: boolean = !checkThresholds || raw.every((entry: Entry): boolean => entry.success); 10 | const data: ReportItem[] = raw.map( 11 | (entry: Entry): ReportItem => ({ 12 | label: entry.url, 13 | // raw: entry, 14 | rawLabel: entry.url, 15 | rawThreshold: entry.threshold, 16 | rawValue: entry.unusedPercentage, 17 | success: entry.success, 18 | threshold: entry.threshold, 19 | thresholdLimit: 'lower', 20 | value: entry.unusedPercentage, 21 | type, 22 | }), 23 | ); 24 | 25 | return { 26 | data: [ 27 | { 28 | data, 29 | label: 'Unused Source Checks', 30 | rawLabel: 'Unused Source Checks', 31 | success, 32 | type, 33 | }, 34 | ], 35 | // raw, 36 | success, 37 | }; 38 | }; 39 | 40 | export default parseReport; 41 | -------------------------------------------------------------------------------- /packages/gimbal/src/module/unused-source/register.ts: -------------------------------------------------------------------------------- 1 | import { register } from '@/module/registry'; 2 | import UnusedSource from '@/module/unused-source'; 3 | import { Report } from '@/typings/command'; 4 | import { Options } from '@/typings/module/registry'; 5 | import meta from './meta'; 6 | 7 | register( 8 | 'unused-source', 9 | meta, 10 | async ({ chrome, commandOptions, url }: Options): Promise => { 11 | const page = await chrome.newPage(); 12 | 13 | if (page) { 14 | const report = await UnusedSource(page, url, commandOptions); 15 | 16 | await page.close(); 17 | 18 | return report; 19 | } 20 | 21 | throw new Error('Could not open page to get unused source'); 22 | }, 23 | ); 24 | -------------------------------------------------------------------------------- /packages/gimbal/src/modules.ts: -------------------------------------------------------------------------------- 1 | import audit from '@/command/audit'; 2 | import { preparseOptions } from '@/command'; 3 | import Config from '@/config'; 4 | 5 | (async (): Promise => { 6 | // Parse config. Audits will consume config automatically 7 | const options = preparseOptions(); 8 | await Config.load(options.cwd, options); 9 | })(); 10 | 11 | // eslint-disable-next-line import/prefer-default-export 12 | export { audit }; 13 | -------------------------------------------------------------------------------- /packages/gimbal/src/output/filter.ts: -------------------------------------------------------------------------------- 1 | import { Report, ReportItem } from '@/typings/command'; 2 | 3 | export const filterReportItemsFailures = (item: ReportItem): ReportItem | undefined => { 4 | if (item.success) { 5 | return undefined; 6 | } 7 | 8 | if (item.data) { 9 | // recursive 10 | const data = item.data.map(filterReportItemsFailures).filter(Boolean) as ReportItem[]; 11 | 12 | return { 13 | ...item, 14 | data, 15 | }; 16 | } 17 | 18 | return item; 19 | }; 20 | 21 | export const returnReportFailures = (report: Report | ReportItem): Report => { 22 | if (report.data) { 23 | const data = report.data.map(filterReportItemsFailures).filter(Boolean) as ReportItem[]; 24 | 25 | return { 26 | ...report, 27 | data, 28 | }; 29 | } 30 | 31 | return report; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/gimbal/src/output/html/index.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from '@modus/gimbal-core/lib/utils/fs'; 2 | import marked from 'marked'; 3 | import path from 'path'; 4 | import generateMarkdown from '@/output/markdown'; 5 | import { Report } from '@/typings/command'; 6 | import { CommandOptions } from '@/typings/utils/command'; 7 | 8 | // use table's html render type when we get to building real html 9 | 10 | const HtmlOutput = async (report: Report, options: CommandOptions): Promise => { 11 | const markdown = await generateMarkdown(report, options); 12 | const html = marked(markdown); 13 | const template = await readFile(path.join(__dirname, 'template.html'), 'utf8'); 14 | 15 | return template.replace('{%body%}', html); 16 | }; 17 | 18 | export default HtmlOutput; 19 | -------------------------------------------------------------------------------- /packages/gimbal/src/output/html/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Gimbal Report 6 | 36 | 37 | 38 | 39 | {%body%} 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/gimbal/src/output/json/index.ts: -------------------------------------------------------------------------------- 1 | import { Report } from '@/typings/command'; 2 | 3 | const JsonOutput = (report: Report): string => JSON.stringify(report, null, 2); 4 | 5 | export default JsonOutput; 6 | -------------------------------------------------------------------------------- /packages/gimbal/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable-next-line import/prefer-default-export */ 2 | export const CHILD_GIMBAL_PROCESS = 'CHILD_GIMBAL_PROCESS'; 3 | -------------------------------------------------------------------------------- /packages/gimbal/src/vcs/index.ts: -------------------------------------------------------------------------------- 1 | import { URL } from 'url'; 2 | import Config from '@/config'; 3 | import { Cls, VCS as VCSTypes } from '@/typings/vcs'; 4 | import GitHubCls from './GitHub'; 5 | 6 | const GIT_URL_RE = /((git|ssh|http(s)?)|(git@[\w.]+))(:(\/\/)?)([\w.@:/\-~]+)(\.git)(\/)?/; 7 | 8 | export const GitHub = 'GitHub'; 9 | 10 | interface Tests { 11 | [label: string]: Cls; 12 | } 13 | 14 | interface VCSConfig { 15 | provider: string; 16 | } 17 | 18 | const tests: Tests = { 19 | [GitHub]: GitHubCls, 20 | }; 21 | 22 | let vcs: GitHubCls | void; 23 | 24 | const normalizeConfiguredVCS = (configuredVCS?: string | VCSConfig): VCSConfig | void => { 25 | if (configuredVCS) { 26 | return typeof configuredVCS === 'string' ? { provider: configuredVCS } : configuredVCS; 27 | } 28 | 29 | return undefined; 30 | }; 31 | 32 | const gitUrlToHttpUrl = (gitUrl: string): string => { 33 | const matches = gitUrl.match(GIT_URL_RE); 34 | 35 | if (!matches) { 36 | return gitUrl; 37 | } 38 | 39 | const ssh = Boolean(matches[4]); 40 | // a ssh url has git@github.com, we just want github.com 41 | const start = ssh ? matches[4].split('@')[1] : matches[7]; 42 | // https url has it part of the 7 index used in start 43 | const end = ssh ? `/${matches[7]}` : ''; 44 | 45 | return `https://${start}${end}`; 46 | }; 47 | 48 | const whichVCS = (repoUrl: string): VCSTypes | void => { 49 | if (vcs) { 50 | return vcs; 51 | } 52 | 53 | const configuredVCS = normalizeConfiguredVCS(Config.get('configs.vcs')); 54 | const url = new URL(gitUrlToHttpUrl(repoUrl)); 55 | const VCS = configuredVCS 56 | ? configuredVCS.provider 57 | : Object.keys(tests).find((key: string): boolean => tests[key].is(url)); 58 | 59 | if (VCS === GitHub) { 60 | vcs = new GitHubCls(); 61 | 62 | return vcs; 63 | } 64 | 65 | return undefined; 66 | }; 67 | 68 | export default whichVCS; 69 | -------------------------------------------------------------------------------- /packages/gimbal/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src", 5 | "bin" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/gimbal/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDirs": ["src"], 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/typings/*": ["../typings/*"], 9 | "@/*": ["./src/*"], 10 | "@modus/gimbal-core": ["../gimbal-core/src/index.ts"], 11 | "@modus/gimbal-core/lib/*": ["../gimbal-core/src/*"], 12 | "@modus/gimbal-plugin-axe": ["../plugin-axe/src/index.ts"], 13 | "@modus/gimbal-plugin-axe/lib/*": ["../plugin-axe/src/*"], 14 | "@modus/gimbal-plugin-last-value": ["../plugin-last-value/src/index.ts"], 15 | "@modus/gimbal-plugin-last-value/lib/*": ["../plugin-last-value/src/*"], 16 | "@modus/gimbal-plugin-mysql": ["../plugin-mysql/src/index.ts"], 17 | "@modus/gimbal-plugin-mysql/lib/*": ["../plugin-mysql/src/*"], 18 | "@modus/gimbal-plugin-source-map-explorer": ["../plugin-source-map-explorer/src/index.ts"], 19 | "@modus/gimbal-plugin-source-map-explorer/lib/*": ["../plugin-source-map-explorer/src/*"], 20 | "@modus/gimbal-plugin-sqlite": ["../plugin-sqlite/src/index.ts"], 21 | "@modus/gimbal-plugin-sqlite/lib/*": ["../plugin-sqlite/src/*"] 22 | } 23 | }, 24 | "exclude": [ 25 | "src/**/*.spec.ts" 26 | ], 27 | "include": [ 28 | "src" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/plugin-axe/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /packages/plugin-axe/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: `${__dirname}/tsconfig.json`, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/plugin-axe/jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest/utils'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | module.exports = { 5 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 6 | prefix: '/', 7 | }), 8 | roots: ['/src'], 9 | transform: { 10 | '^.+\\.tsx?$': 'ts-jest', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/plugin-axe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modus/gimbal-plugin-axe", 3 | "version": "1.2.6", 4 | "description": "Plugin to add axe audits to Gimbal", 5 | "author": "Mitchell Simoens ", 6 | "homepage": "https://github.com/ModusCreateOrg/gimbal#readme", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "files": [ 10 | "lib" 11 | ], 12 | "scripts": { 13 | "build": "ttsc -p .", 14 | "build:check": "ttsc --noEmit -p .", 15 | "build:watch": "ttsc --watch -p .", 16 | "link": "npm link", 17 | "lint": "eslint 'src/**/*.ts'", 18 | "prepublish": "yarn build", 19 | "test": "jest --coverage", 20 | "test:coveralls": "cat ./coverage/lcov.info | coveralls", 21 | "test:nocov": "jest" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "keywords": [ 27 | "gimbal", 28 | "axe", 29 | "puppeteer", 30 | "audit", 31 | "typescript", 32 | "javascript", 33 | "webdevelopment", 34 | "ci" 35 | ], 36 | "repository": { 37 | "type": "git", 38 | "url": "git+https://github.com/ModusCreateOrg/gimbal.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/ModusCreateOrg/gimbal/issues" 42 | }, 43 | "dependencies": { 44 | "axe-puppeteer": "1.0.0", 45 | "deepmerge": "4.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/plugin-axe/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDirs": ["src"], 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/typings/*": ["../typings/*"], 9 | "@modus/gimbal-core": ["../gimbal-core/src/index.ts"], 10 | "@modus/gimbal-core/lib/*": ["../gimbal-core/src/*"] 11 | } 12 | }, 13 | "exclude": [ 14 | "src/**/*.spec.ts" 15 | ], 16 | "include": [ 17 | "src" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/plugin-last-value/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /packages/plugin-last-value/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: `${__dirname}/tsconfig.json`, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/plugin-last-value/jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest/utils'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | module.exports = { 5 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 6 | prefix: '/', 7 | }), 8 | roots: ['/src'], 9 | transform: { 10 | '^.+\\.tsx?$': 'ts-jest', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/plugin-last-value/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modus/gimbal-plugin-last-value", 3 | "version": "1.2.6", 4 | "description": "Track last value for Gimbal audits", 5 | "homepage": "https://github.com/ModusCreateOrg/gimbal#readme", 6 | "license": "MIT", 7 | "main": "lib/plugin-last-value/src/index.js", 8 | "files": [ 9 | "lib" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ModusCreateOrg/gimbal.git" 17 | }, 18 | "scripts": { 19 | "build": "ttsc -p .", 20 | "build:check": "ttsc --noEmit -p .", 21 | "build:cleanup": "rimraf lib/gimbal-core", 22 | "build:watch": "ttsc --watch -p .", 23 | "link": "npm link", 24 | "lint": "eslint 'src/**/*.ts'", 25 | "postbuild": "yarn run build:cleanup", 26 | "prepublish": "yarn build", 27 | "test": "jest --coverage", 28 | "test:coveralls": "cat ./coverage/lcov.info | coveralls", 29 | "test:nocov": "jest" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/ModusCreateOrg/gimbal/issues" 33 | }, 34 | "dependencies": { 35 | "@modus/gimbal-core": "1.2.6", 36 | "bytes": "3.1.0", 37 | "deepmerge": "4.0.0" 38 | }, 39 | "devDependencies": { 40 | "@types/bytes": "3.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/plugin-last-value/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDirs": ["src"], 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/typings/*": ["../typings/*"], 9 | "@/*": ["./src/*"], 10 | "@modus/gimbal-core": ["../gimbal-core/src/index.ts"], 11 | "@modus/gimbal-core/lib/*": ["../gimbal-core/src/*"] 12 | } 13 | }, 14 | "exclude": [ 15 | "src/**/*.spec.ts" 16 | ], 17 | "include": [ 18 | "src" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/plugin-mysql/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /packages/plugin-mysql/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: `${__dirname}/tsconfig.json`, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/plugin-mysql/jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest/utils'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | module.exports = { 5 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 6 | prefix: '/', 7 | }), 8 | roots: ['/src'], 9 | transform: { 10 | '^.+\\.tsx?$': 'ts-jest', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/plugin-mysql/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modus/gimbal-plugin-mysql", 3 | "version": "1.2.6", 4 | "description": "MySQL storage helpers for Gimbal and Gimbal plugins", 5 | "homepage": "https://github.com/ModusCreateOrg/gimbal#readme", 6 | "license": "MIT", 7 | "main": "lib/plugin-mysql/src/index.js", 8 | "files": [ 9 | "lib" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ModusCreateOrg/gimbal.git" 17 | }, 18 | "scripts": { 19 | "build": "ttsc -p .", 20 | "build:check": "ttsc --noEmit -p .", 21 | "build:cleanup": "rimraf lib/gimbal-core", 22 | "build:watch": "ttsc --watch -p .", 23 | "link": "npm link", 24 | "lint": "eslint 'src/**/*.ts'", 25 | "postbuild": "yarn run build:cleanup", 26 | "prepublish": "yarn build", 27 | "test": "jest --coverage", 28 | "test:coveralls": "cat ./coverage/lcov.info | coveralls", 29 | "test:nocov": "jest" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/ModusCreateOrg/gimbal/issues" 33 | }, 34 | "dependencies": { 35 | "@modus/gimbal-core": "1.2.6", 36 | "deepmerge": "4.0.0", 37 | "mysql": "2.17.1" 38 | }, 39 | "devDependencies": { 40 | "@types/mysql": "2.15.7" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/plugin-mysql/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDirs": ["src"], 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/typings/*": ["../typings/*"], 9 | "@modus/gimbal-core": ["../gimbal-core/src/index.ts"], 10 | "@modus/gimbal-core/lib/*": ["../gimbal-core/src/*"] 11 | } 12 | }, 13 | "exclude": [ 14 | "src/**/*.spec.ts" 15 | ], 16 | "include": [ 17 | "src" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/plugin-source-map-explorer/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /packages/plugin-source-map-explorer/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: `${__dirname}/tsconfig.json`, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/plugin-source-map-explorer/jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest/utils'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | module.exports = { 5 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 6 | prefix: '/', 7 | }), 8 | roots: ['/src'], 9 | transform: { 10 | '^.+\\.tsx?$': 'ts-jest', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/plugin-source-map-explorer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modus/gimbal-plugin-source-map-explorer", 3 | "version": "1.2.6", 4 | "description": "Adds Source Map Explorer auditing to Gimbal", 5 | "homepage": "https://github.com/ModusCreateOrg/gimbal#readme", 6 | "license": "MIT", 7 | "main": "lib/plugin-source-map-explorer/src/index.js", 8 | "files": [ 9 | "lib" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ModusCreateOrg/gimbal.git" 17 | }, 18 | "scripts": { 19 | "build": "ttsc -p .", 20 | "build:check": "ttsc --noEmit -p .", 21 | "build:cleanup": "rimraf lib/gimbal-core", 22 | "build:watch": "ttsc --watch -p .", 23 | "link": "npm link", 24 | "lint": "eslint 'src/**/*.ts'", 25 | "postbuild": "yarn run build:cleanup", 26 | "prepublish": "yarn build", 27 | "test": "jest --coverage", 28 | "test:coveralls": "cat ./coverage/lcov.info | coveralls", 29 | "test:nocov": "jest" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/ModusCreateOrg/gimbal/issues" 33 | }, 34 | "keywords": [ 35 | "gimbal", 36 | "sourcemap", 37 | "source", 38 | "map", 39 | "webpack", 40 | "audit", 41 | "typescript", 42 | "javascript", 43 | "webdevelopment", 44 | "ci" 45 | ], 46 | "dependencies": { 47 | "@modus/gimbal-core": "1.2.6", 48 | "bytes": "3.1.0", 49 | "deepmerge": "4.0.0", 50 | "globby": "10.0.1", 51 | "minimatch": "3.0.4", 52 | "source-map-explorer": "2.1.0" 53 | }, 54 | "devDependencies": { 55 | "@types/bytes": "3.0.0", 56 | "@types/globby": "9.1.0", 57 | "@types/minimatch": "3.0.3" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/plugin-source-map-explorer/src/config.spec.ts: -------------------------------------------------------------------------------- 1 | import { defaultConfig, meta, type } from './config'; 2 | 3 | describe('@modus/gimbal-plugin-source-map-explorer/config', (): void => { 4 | it('should export properly', (): void => { 5 | expect(defaultConfig).toEqual({ 6 | bundles: ['**/*.js'], 7 | }); 8 | 9 | expect(meta).toEqual({ 10 | maxNumRoutes: 1, 11 | thresholdLimit: 'upper', 12 | thresholdType: 'size', 13 | }); 14 | 15 | expect(type).toBe('source-map-explorer'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/plugin-source-map-explorer/src/config.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from '@/typings/module'; 2 | 3 | export interface BundleThreshold { 4 | [file: string]: string; 5 | } 6 | 7 | export interface BundleObject { 8 | disable?: boolean; 9 | path: string; 10 | thresholds: BundleThreshold; 11 | } 12 | 13 | export type BundleType = string | BundleObject; 14 | 15 | export interface Config { 16 | bundles: BundleType[]; 17 | } 18 | 19 | export const defaultConfig: Config = { 20 | bundles: ['**/*.js'], 21 | }; 22 | 23 | export const meta: Meta = { 24 | maxNumRoutes: 1, 25 | thresholdLimit: 'upper', 26 | thresholdType: 'size', 27 | }; 28 | 29 | export const type = 'source-map-explorer'; 30 | -------------------------------------------------------------------------------- /packages/plugin-source-map-explorer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDirs": ["src"], 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/typings/*": ["../typings/*"], 9 | "@modus/gimbal-core": ["../gimbal-core/src/index.ts"], 10 | "@modus/gimbal-core/lib/*": ["../gimbal-core/src/*"] 11 | } 12 | }, 13 | "exclude": [ 14 | "src/**/*.spec.ts" 15 | ], 16 | "include": [ 17 | "src" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/plugin-sqlite/.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.spec.ts 2 | -------------------------------------------------------------------------------- /packages/plugin-sqlite/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: `${__dirname}/tsconfig.json`, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/plugin-sqlite/jest.config.js: -------------------------------------------------------------------------------- 1 | const { pathsToModuleNameMapper } = require('ts-jest/utils'); 2 | const { compilerOptions } = require('./tsconfig'); 3 | 4 | module.exports = { 5 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { 6 | prefix: '/', 7 | }), 8 | roots: ['/src'], 9 | transform: { 10 | '^.+\\.tsx?$': 'ts-jest', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/plugin-sqlite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modus/gimbal-plugin-sqlite", 3 | "version": "1.2.6", 4 | "description": "SQLite storage helpers for Gimbal and Gimbal plugins", 5 | "homepage": "https://github.com/ModusCreateOrg/gimbal#readme", 6 | "license": "MIT", 7 | "main": "lib/plugin-sqlite/src/index.js", 8 | "files": [ 9 | "lib" 10 | ], 11 | "publishConfig": { 12 | "access": "public" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/ModusCreateOrg/gimbal.git" 17 | }, 18 | "scripts": { 19 | "build": "ttsc -p .", 20 | "build:check": "ttsc --noEmit -p .", 21 | "build:cleanup": "rimraf lib/gimbal-core", 22 | "build:watch": "ttsc --watch -p .", 23 | "link": "npm link", 24 | "lint": "eslint 'src/**/*.ts'", 25 | "postbuild": "yarn run build:cleanup", 26 | "prepublish": "yarn build", 27 | "test": "jest --coverage", 28 | "test:coveralls": "cat ./coverage/lcov.info | coveralls", 29 | "test:nocov": "jest" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/ModusCreateOrg/gimbal/issues" 33 | }, 34 | "dependencies": { 35 | "@modus/gimbal-core": "1.2.6", 36 | "deepmerge": "4.0.0", 37 | "mkdirp": "0.5.1", 38 | "sqlite3": "4.1.0" 39 | }, 40 | "devDependencies": { 41 | "@types/mkdirp": "0.5.2", 42 | "@types/sqlite3": "3.1.5" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/plugin-sqlite/src/index.ts: -------------------------------------------------------------------------------- 1 | import { resolvePath } from '@modus/gimbal-core/lib/utils/fs'; 2 | import deepmerge from 'deepmerge'; 3 | import mkdirpMod from 'mkdirp'; 4 | import { dirname } from 'path'; 5 | import sqlite3 from 'sqlite3'; 6 | import { promisify } from 'util'; 7 | import { PluginOptions } from '@/typings/config/plugin'; 8 | import { GetEvent, SaveEvent } from '@/typings/plugin/last-value'; 9 | 10 | const mkdirp = promisify(mkdirpMod); 11 | 12 | interface ItemConfig { 13 | table: string; 14 | } 15 | 16 | type Item = boolean | ItemConfig; 17 | 18 | interface Config { 19 | commandPrefix?: string | string[]; 20 | file?: string; 21 | lastValue?: Item; 22 | } 23 | 24 | const defaultConfig: Config = { 25 | file: 'gimbal.db', 26 | lastValue: false, 27 | }; 28 | 29 | const willNeedDatabase = (config: Config): boolean => config.lastValue !== false; 30 | 31 | const sqlite = async ({ bus, commandOptions }: PluginOptions, config: Config): Promise => { 32 | const sqliteConfig = deepmerge(defaultConfig, config); 33 | 34 | if (commandOptions && sqliteConfig.file !== ':memory:') { 35 | sqliteConfig.file = resolvePath(commandOptions.cwd, sqliteConfig.file); 36 | } 37 | 38 | if (willNeedDatabase(sqliteConfig)) { 39 | if (sqliteConfig.file !== ':memory:') { 40 | await mkdirp(dirname(sqliteConfig.file)); 41 | } 42 | 43 | const event = await bus('event'); 44 | const db = new sqlite3.Database(sqliteConfig.file); 45 | 46 | if (config.lastValue) { 47 | const { getLastReport, init, saveLastReport } = await import('./last-value'); 48 | const pluginConfig = { 49 | ...sqliteConfig, 50 | ...deepmerge( 51 | { 52 | table: 'gimbal_archive', 53 | }, 54 | config.lastValue === true ? {} : config.lastValue, 55 | ), 56 | db, 57 | }; 58 | 59 | await init(pluginConfig); 60 | 61 | event.on( 62 | 'plugin/last-value/report/get', 63 | (_eventName: string, { command }: GetEvent): Promise => getLastReport(command, pluginConfig), 64 | ); 65 | 66 | event.on( 67 | 'plugin/last-value/report/save', 68 | (_eventName: string, { command, report }: SaveEvent): Promise => 69 | saveLastReport(command, report, pluginConfig), 70 | ); 71 | } 72 | } 73 | }; 74 | 75 | export default sqlite; 76 | -------------------------------------------------------------------------------- /packages/plugin-sqlite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "lib", 5 | "rootDirs": ["src"], 6 | "baseUrl": ".", 7 | "paths": { 8 | "@/typings/*": ["../typings/*"], 9 | "@modus/gimbal-core": ["../gimbal-core/src/index.ts"], 10 | "@modus/gimbal-core/lib/*": ["../gimbal-core/src/*"] 11 | } 12 | }, 13 | "exclude": [ 14 | "src/**/*.spec.ts" 15 | ], 16 | "include": [ 17 | "src" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/typings/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: `${__dirname}/tsconfig.json`, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /packages/typings/ci/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface Cls { 2 | is: () => boolean; 3 | name: string; 4 | } 5 | 6 | export type CIMode = 'commit' | 'pr'; 7 | -------------------------------------------------------------------------------- /packages/typings/command/index.d.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions } from '@/typings/utils/command'; 2 | 3 | export type ReportThresholdLimit = 'lower' | 'upper'; 4 | 5 | export interface ReportError { 6 | message: string; 7 | stack: string[]; 8 | } 9 | 10 | export interface ReportItem { 11 | data?: ReportItem[]; 12 | label: string; 13 | lastValue?: number | string; 14 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 15 | raw?: any; 16 | rawLabel: string; 17 | rawLastValue?: number | string; 18 | rawThreshold?: number | string; 19 | rawValue?: number | string; 20 | threshold?: number | string; 21 | thresholdLimit?: ReportThresholdLimit; 22 | value?: number | string; 23 | success: boolean; 24 | type: string; 25 | } 26 | 27 | export interface Report { 28 | error?: ReportError; 29 | data?: ReportItem[]; 30 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 31 | raw?: any; 32 | rawReports?: Report[]; 33 | success: boolean; 34 | } 35 | 36 | export interface StartEvent { 37 | args: string[]; 38 | commandOptions: CommandOptions; 39 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 40 | command: any; // would cause circular dependency if imported the command class 41 | } 42 | 43 | export interface EndEvent { 44 | args: string[]; 45 | commandOptions: CommandOptions; 46 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 47 | command: any; // would cause circular dependency if imported the command class 48 | report: Report; 49 | } 50 | 51 | export interface ActionStartEvent { 52 | args: string[]; 53 | commandOptions: CommandOptions; 54 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 55 | command: any; // would cause circular dependency if imported the command class 56 | } 57 | 58 | export interface ActionEndEvent { 59 | args: string[]; 60 | commandOptions: CommandOptions; 61 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 62 | command: any; // would cause circular dependency if imported the command class 63 | report: Report; 64 | } 65 | -------------------------------------------------------------------------------- /packages/typings/components/Table/index.d.ts: -------------------------------------------------------------------------------- 1 | import { TableInstanceOptions } from 'cli-table3'; 2 | 3 | export type Alignments = 'left' | 'center' | 'right'; 4 | 5 | export interface Column { 6 | align?: Alignments; 7 | header: string; 8 | key: string; 9 | maxWidth?: number; 10 | renderer?: Renderer; 11 | } 12 | 13 | export interface Config { 14 | columns?: Column[]; 15 | data?: Data[]; 16 | options?: TableInstanceOptions; 17 | } 18 | 19 | export type Finder = (item: Column | Data, index: number) => boolean; 20 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 21 | export type Data = any; 22 | export type Renderer = (value: Data, item: Data) => string | Promise; 23 | export type Renders = 'cli' | 'html' | 'markdown'; 24 | 25 | export interface RendererArgs { 26 | columns: Column[]; 27 | data: Data[]; 28 | options?: TableInstanceOptions; 29 | } 30 | 31 | export interface Table { 32 | add: (item: Data, index?: number) => void; 33 | addColumn: (column: Column, index?: number) => void; 34 | find: (callback: Finder, getIndex: boolean) => Data | number | void; 35 | findColumn: (callback: Finder, getIndex: boolean) => Column | number | void; 36 | get: (index: number) => Data | void; 37 | getColumn: (index: number) => Column | void; 38 | remove: (item: Data) => void; 39 | removeColumn: (column: Column) => void; 40 | render: (type: Renders) => Promise; 41 | set: (data: Data[]) => void; 42 | } 43 | -------------------------------------------------------------------------------- /packages/typings/config/index.d.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import lighthouse from 'lighthouse'; 3 | import { Plugin } from '@/typings/config/plugin'; 4 | import { Modules } from '@/typings/module'; 5 | import { Config as HeapSnapshotConfig } from '@/typings/module/heap-snapshot'; 6 | import { SizeConfigs } from '@/typings/module/size'; 7 | import { UnusedSourceConfig } from '@/typings/module/unused-source'; 8 | import { CommandOptions } from '@/typings/utils/command'; 9 | 10 | export type PluginType = string | Plugin; 11 | 12 | export interface Configs { 13 | 'heap-snapshot': HeapSnapshotConfig; 14 | lighthouse: lighthouse.Config.Json; 15 | size: SizeConfigs[]; 16 | 'unused-source': UnusedSourceConfig; 17 | } 18 | 19 | export interface Outputs { 20 | html?: string; 21 | json?: string; 22 | markdown?: string; 23 | } 24 | 25 | export interface Config { 26 | audits?: Modules[]; 27 | configs?: Configs; 28 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 29 | jobs?: any; // still have to decide what this will look like fully 30 | outputs?: Outputs; 31 | plugins?: string[]; 32 | } 33 | 34 | export type LoaderFn = (file: string) => Promise; 35 | 36 | export interface LoaderMap { 37 | js: LoaderFn; 38 | json: LoaderFn; 39 | yaml: LoaderFn; 40 | yml: LoaderFn; 41 | [name: string]: LoaderFn; 42 | } 43 | 44 | export interface LoadStartEvent { 45 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 46 | Config: any; // would cause circular dependency if imported the command class 47 | commandOptions: CommandOptions; 48 | dir: string; 49 | file: string; 50 | force: boolean; 51 | } 52 | 53 | export interface LoadEndEvent extends LoadStartEvent { 54 | config: Config; 55 | } 56 | -------------------------------------------------------------------------------- /packages/typings/config/jobs.d.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions } from '@/typings/utils/command'; 2 | import { CmdSpawnRet } from '@/typings/utils/spawn'; 3 | 4 | export interface Options { 5 | [key: string]: string; 6 | } 7 | 8 | export interface OptionsCt { 9 | options: Options; 10 | } 11 | 12 | export type ArrayJob = [string, OptionsCt]; 13 | export type Job = string | ArrayJob; 14 | export type JobRet = CmdSpawnRet | void; 15 | 16 | export interface JobStartEvent { 17 | args: string[]; 18 | commandOptions: CommandOptions; 19 | } 20 | 21 | export interface JobEndSuccessEvent { 22 | args: string[]; 23 | commandOptions: CommandOptions; 24 | ret: CmdSpawnRet; 25 | } 26 | 27 | export interface JobEndFailureEvent { 28 | args: string[]; 29 | commandOptions: CommandOptions; 30 | error: Error; 31 | } 32 | 33 | export type JobEndEvent = JobEndSuccessEvent | JobEndFailureEvent; 34 | 35 | export interface JobsStartEvent { 36 | commandOptions: CommandOptions; 37 | jobs: Job[]; 38 | } 39 | 40 | export interface JobsEndEvent { 41 | commandOptions: CommandOptions; 42 | jobs: Job[]; 43 | ret: JobRet[]; 44 | } 45 | -------------------------------------------------------------------------------- /packages/typings/config/plugin/index.d.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions } from '@/typings/utils/command'; 2 | 3 | export interface PluginOptions { 4 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 5 | bus: (path: string) => any; 6 | commandOptions?: CommandOptions; 7 | dir: string; 8 | } 9 | 10 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 11 | export type PluginFunction = (options: PluginOptions, config: Config) => any; 12 | 13 | export interface Plugin { 14 | default: PluginFunction; 15 | } 16 | 17 | export interface Config { 18 | name: string; 19 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 20 | [key: string]: any; 21 | } 22 | 23 | export interface PluginConfig extends Config { 24 | enabled?: boolean; 25 | plugin: string | Plugin; 26 | } 27 | -------------------------------------------------------------------------------- /packages/typings/event/Event.d.ts: -------------------------------------------------------------------------------- 1 | import { Data } from '@/typings/utils/Queue'; 2 | 3 | export type Callback = (event: string, data: Data) => Data | void; 4 | 5 | export type CreatedCallback = (data: Data) => Data | void; 6 | 7 | export type CreateCallback = (event: string) => CreatedCallback; 8 | 9 | export type Fire = (event: string, data: Data) => Data; 10 | 11 | export interface Config { 12 | fn: Callback; 13 | priority?: number; 14 | } 15 | 16 | export interface Event { 17 | priority: number; 18 | fire: Fire; 19 | createCallback: CreateCallback; 20 | } 21 | -------------------------------------------------------------------------------- /packages/typings/event/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Callback, Config, Event } from '@/typings/event/Event'; 2 | import { Data } from '@/typings/utils/Queue'; 3 | 4 | export interface Emitter { 5 | fire: (event: string, data: Data) => Promise; 6 | on: (event: string, config: Callback | Config) => Event; 7 | } 8 | 9 | export interface FireRet { 10 | data: Data; 11 | rets: (Data | void)[]; 12 | } 13 | -------------------------------------------------------------------------------- /packages/typings/logger/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface SpinniesConfig { 2 | color?: string; 3 | disableSpins?: boolean; 4 | failColor?: string; 5 | failPrefix?: string; 6 | spinnerColor?: string; 7 | succeedColor?: string; 8 | succeedPrefix?: string; 9 | spinner?: { 10 | frames: string[]; 11 | interval: number; 12 | }; 13 | } 14 | 15 | export interface SpinniesOptions { 16 | color?: string; 17 | failColor?: string; 18 | status?: 'fail' | 'non-spinnable' | 'spinning' | 'stopped' | 'succeed'; 19 | succeedColor?: string; 20 | text: string; 21 | } 22 | 23 | export interface SpinniesFailure { 24 | failColor?: string; 25 | text: string; 26 | } 27 | 28 | export interface SpinniesSuccess { 29 | succeedColor?: string; 30 | text: string; 31 | } 32 | 33 | export type SpinniesFinish = SpinniesFailure | SpinniesSuccess; 34 | 35 | /* eslint-disable-next-line @typescript-eslint/no-empty-interface */ 36 | interface DeepArray extends Array> {} 37 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 38 | export type LoggerArgs = DeepArray; 39 | 40 | export type LoggerFunction = (...val: LoggerArgs) => void; 41 | export type LoggerGroupFunction = (...groups: LoggerArgs) => void; 42 | export interface LoggerGroup { 43 | [name: string]: LoggerGroupFunction; 44 | } 45 | 46 | export interface Logger { 47 | error: LoggerFunction; 48 | group: LoggerGroup; 49 | log: LoggerFunction; 50 | verbose: LoggerFunction; 51 | [name: string]: LoggerFunction | LoggerGroup; 52 | } 53 | -------------------------------------------------------------------------------- /packages/typings/module/chrome/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Browser, LaunchOptions, Page } from 'puppeteer'; 2 | 3 | export interface LaunchStartEvent { 4 | config: LaunchOptions; 5 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 6 | mod: any; // would cause circular dependency if imported the command class 7 | } 8 | 9 | export interface LaunchEndEvent { 10 | browser: Browser; 11 | config: LaunchOptions; 12 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 13 | mod: any; // would cause circular dependency if imported the command class 14 | } 15 | 16 | export interface KillStartEvent { 17 | browser: Browser; 18 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 19 | mod: any; // would cause circular dependency if imported the command class 20 | } 21 | 22 | export interface KillEndEvent { 23 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 24 | mod: any; // would cause circular dependency if imported the command class 25 | } 26 | 27 | export interface NewPageStartEvent { 28 | browser: Browser; 29 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 30 | mod: any; // would cause circular dependency if imported the command class 31 | } 32 | 33 | export interface NewPageEndEvent { 34 | browser: Browser; 35 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 36 | mod: any; // would cause circular dependency if imported the command class 37 | page: Page; 38 | } 39 | -------------------------------------------------------------------------------- /packages/typings/module/heap-snapshot/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Metrics, Page } from 'puppeteer'; 2 | import { Report } from '@/typings/command'; 3 | import { CommandOptions } from '@/typings/utils/command'; 4 | import { AdvancedThreshold } from '@/typings/utils/threshold'; 5 | 6 | export interface Config { 7 | threshold: AdvancedThreshold; 8 | } 9 | 10 | export interface HeapMetrics extends Metrics { 11 | [label: string]: number; 12 | } 13 | 14 | export interface NavigationStartEvent { 15 | config: Config; 16 | page: Page; 17 | options: CommandOptions; 18 | url: string; 19 | } 20 | 21 | export interface NavigationEndEvent { 22 | config: Config; 23 | page: Page; 24 | options: CommandOptions; 25 | url: string; 26 | } 27 | 28 | export interface AuditStartEvent { 29 | config: Config; 30 | page: Page; 31 | options: CommandOptions; 32 | url: string; 33 | } 34 | 35 | export interface AuditEndEvent { 36 | audit: Metrics; 37 | config: Config; 38 | page: Page; 39 | options: CommandOptions; 40 | url: string; 41 | } 42 | 43 | export interface ReportStartEvent { 44 | audit: Metrics; 45 | config: Config; 46 | page: Page; 47 | options: CommandOptions; 48 | url: string; 49 | } 50 | 51 | export interface ReportEndEvent { 52 | audit: Metrics; 53 | config: Config; 54 | page: Page; 55 | options: CommandOptions; 56 | report: Report; 57 | url: string; 58 | } 59 | -------------------------------------------------------------------------------- /packages/typings/module/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ReportThresholdLimit } from '@/typings/command'; 2 | 3 | export type Modules = 'heap-snapshot' | 'lighthouse' | 'size' | 'unused-source'; 4 | 5 | export type Types = 'number' | 'percentage' | 'size'; 6 | 7 | interface TypesMap { 8 | [label: string]: Types; 9 | } 10 | 11 | /* eslint-disable-next-line import/prefer-default-export */ 12 | export interface Meta { 13 | maxNumRoutes?: number; 14 | thresholdLimit: ReportThresholdLimit; 15 | thresholdType?: Types; 16 | thresholdTypes?: TypesMap; 17 | } 18 | -------------------------------------------------------------------------------- /packages/typings/module/lighthouse/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Report } from '@/typings/command'; 2 | import { Threshold } from '@/typings/utils/threshold'; 3 | 4 | export interface AuditRef { 5 | group?: string; 6 | id: string; 7 | weight: number; 8 | } 9 | 10 | export interface Category { 11 | auditRefs: AuditRef[]; 12 | description?: string; 13 | id: string; 14 | manualDescription?: string; 15 | score: number; 16 | title: string; 17 | } 18 | 19 | export interface Categories { 20 | [name: string]: Category; 21 | } 22 | 23 | export interface ConfigSettings { 24 | skipAudits?: string[]; 25 | } 26 | 27 | export interface Config { 28 | extends?: string; 29 | outputHtml?: string; 30 | settings?: ConfigSettings; 31 | threshold: Threshold; 32 | } 33 | 34 | export interface Options { 35 | chromePort: string; 36 | output?: string[]; 37 | } 38 | 39 | export interface Result { 40 | audits: {}; 41 | categories: Categories; 42 | categoryGroups: {}; 43 | configSettings: {}; 44 | environment: {}; 45 | fetchTime: string; 46 | finalUrl: string; 47 | i18n: {}; 48 | lighthouseVersion: string; 49 | requestedUrl: string; 50 | runWarnings: string[]; 51 | timing: {}; 52 | userAgent: string; 53 | } 54 | 55 | export interface Audit { 56 | lhr: Result; 57 | report?: string[]; 58 | } 59 | 60 | export interface AuditStartEvent { 61 | config: Config; 62 | options: Options; 63 | url: string; 64 | } 65 | 66 | export interface AuditEndEvent { 67 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 68 | audit: any; // lighthouse doesn't have types yet 69 | config: Config; 70 | options: Options; 71 | url: string; 72 | } 73 | 74 | export interface ReportStartEvent { 75 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 76 | audit: any; // lighthouse doesn't have types yet 77 | config: Config; 78 | options: Options; 79 | url: string; 80 | } 81 | 82 | export interface ReportEndEvent { 83 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 84 | audit: any; // lighthouse doesn't have types yet 85 | config: Config; 86 | options: Options; 87 | report: Report; 88 | url: string; 89 | } 90 | -------------------------------------------------------------------------------- /packages/typings/module/registry.d.ts: -------------------------------------------------------------------------------- 1 | import { Report } from '@/typings/command'; 2 | import { Meta } from '@/typings/module'; 3 | import { CommandOptions } from '@/typings/utils/command'; 4 | 5 | export interface Options { 6 | commandOptions: CommandOptions; 7 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 8 | [name: string]: any; // any to allow anything else to be on this options object 9 | } 10 | 11 | export type Module = (options: Options) => Promise; 12 | 13 | export interface ModuleInfo { 14 | fn: Module; 15 | meta: Meta; 16 | } 17 | 18 | export type Get = (name: string) => Module | void; 19 | 20 | export type Register = (name: string, meta: Meta, fn: Module) => void; 21 | -------------------------------------------------------------------------------- /packages/typings/module/serve/index.d.ts: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | 3 | export interface CreateServerStartEvent { 4 | dir: string; 5 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 6 | mod: any; // would cause circular dependency if imported the command class 7 | port: number; 8 | } 9 | 10 | export interface CreateServerEndEvent { 11 | dir: string; 12 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 13 | mod: any; // would cause circular dependency if imported the command class 14 | port: number; 15 | server: http.Server; 16 | } 17 | 18 | export interface ListenStartEvent { 19 | dir: string; 20 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 21 | mod: any; // would cause circular dependency if imported the command class 22 | port: number; 23 | server: http.Server; 24 | } 25 | 26 | export interface ListenEndEvent { 27 | dir: string; 28 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 29 | mod: any; // would cause circular dependency if imported the command class 30 | port: number; 31 | server: http.Server; 32 | } 33 | 34 | export interface CloseStartEvent { 35 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 36 | mod: any; // would cause circular dependency if imported the command class 37 | server: http.Server; 38 | } 39 | 40 | export interface CloseEndEvent { 41 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 42 | mod: any; // would cause circular dependency if imported the command class 43 | } 44 | -------------------------------------------------------------------------------- /packages/typings/module/size/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Report } from '@/typings/command'; 2 | import { CommandOptions } from '@/typings/utils/command'; 3 | 4 | export interface SizeConfigs { 5 | maxSize: string; 6 | path: string; 7 | type?: string; 8 | } 9 | 10 | export interface SizeConfig { 11 | compression?: 'brotli' | 'gzip'; 12 | threshold: SizeConfigs[]; 13 | } 14 | 15 | export interface FileResult { 16 | filePath: string; 17 | isDirectory: boolean; 18 | maxSizeBytes: number; 19 | maxSize: string; 20 | sizeBytes: number; 21 | size: string; 22 | thresholdPath: string; 23 | } 24 | 25 | export interface AuditStartEvent { 26 | config: SizeConfig; 27 | options: CommandOptions; 28 | } 29 | 30 | export interface AuditEndEvent { 31 | audit: FileResult[]; 32 | config: SizeConfig; 33 | options: CommandOptions; 34 | } 35 | 36 | export interface ReportStartEvent { 37 | audit: FileResult[]; 38 | config: SizeConfig; 39 | options: CommandOptions; 40 | } 41 | 42 | export interface ReportEndEvent { 43 | audit: FileResult[]; 44 | config: SizeConfig; 45 | options: CommandOptions; 46 | report: Report; 47 | } 48 | -------------------------------------------------------------------------------- /packages/typings/output/cli.d.ts: -------------------------------------------------------------------------------- 1 | import { Table } from '@/typings/components/Table'; 2 | 3 | /* eslint-disable-next-line import/prefer-default-export */ 4 | export interface CliOutputOptions { 5 | table?: Table; 6 | } 7 | -------------------------------------------------------------------------------- /packages/typings/output/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Table } from '@/typings/components/Table'; 2 | import { Report } from '@/typings/command'; 3 | import { CliOutputOptions } from '@/typings/output/cli'; 4 | import { CommandOptions } from '@/typings/utils/command'; 5 | 6 | export interface FileWriteStartEvent { 7 | contents: string; 8 | file: string; 9 | type: string; 10 | } 11 | 12 | export interface FileWriteEndEvent { 13 | contents: string; 14 | file: string; 15 | type: string; 16 | } 17 | 18 | export interface CliReportStartEvent { 19 | commandOptions: CommandOptions; 20 | cliOptions: CliOutputOptions; 21 | report: Report; 22 | } 23 | 24 | export interface CliReportEndEvent { 25 | commandOptions: CommandOptions; 26 | report: Report; 27 | table: Table | void; 28 | } 29 | 30 | export interface CliWriteStartEvent { 31 | commandOptions: CommandOptions; 32 | report: Report; 33 | table: Table | void; 34 | } 35 | 36 | export interface CliWriteEndEvent { 37 | commandOptions: CommandOptions; 38 | contents: string; 39 | report: Report; 40 | table: Table | void; 41 | } 42 | 43 | export interface HtmlReportStartEvent { 44 | commandOptions: CommandOptions; 45 | file: string; 46 | report: Report; 47 | } 48 | 49 | export interface HtmlReportEndEvent { 50 | commandOptions: CommandOptions; 51 | contents: string; 52 | file: string; 53 | report: Report; 54 | } 55 | 56 | export interface JsonReportStartEvent { 57 | commandOptions: CommandOptions; 58 | file: string; 59 | report: Report; 60 | } 61 | 62 | export interface JsonReportEndEvent { 63 | commandOptions: CommandOptions; 64 | contents: string; 65 | file: string; 66 | report: Report; 67 | } 68 | 69 | export interface MarkdownReportStartEvent { 70 | commandOptions: CommandOptions; 71 | file: string; 72 | report: Report; 73 | } 74 | 75 | export interface MarkdownReportEndEvent { 76 | commandOptions: CommandOptions; 77 | contents: string; 78 | file: string; 79 | report: Report; 80 | } 81 | 82 | export interface OutputItemObject { 83 | onlyFailures?: boolean; 84 | path: string; 85 | } 86 | 87 | export type OutputItem = string | OutputItemObject; 88 | 89 | export type OutputFn = (report: Report, commandOptions: CommandOptions, location: string) => Promise; 90 | -------------------------------------------------------------------------------- /packages/typings/output/markdown/index.d.ts: -------------------------------------------------------------------------------- 1 | import { Report } from '@/typings/command'; 2 | import { Table } from '@/typings/components/Table'; 3 | import { CliOutputOptions } from '@/typings/output/cli'; 4 | import { CommandOptions } from '@/typings/utils/command'; 5 | 6 | export interface MarkdownRenderTableStartEvent { 7 | commandOptions: CommandOptions; 8 | options?: CliOutputOptions; 9 | report: Report; 10 | table: Table; 11 | } 12 | 13 | export interface MarkdownRenderTableEndEvent extends MarkdownRenderTableStartEvent { 14 | markdown: string; 15 | } 16 | -------------------------------------------------------------------------------- /packages/typings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@modus/gimbal-typings", 3 | "private": true, 4 | "version": "1.2.6", 5 | "description": "A common node module that doesn't get published to npm holding the typings", 6 | "homepage": "https://github.com/ModusCreateOrg/gimbal#readme", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/ModusCreateOrg/gimbal.git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/ModusCreateOrg/gimbal/issues" 14 | }, 15 | "dependencies": { 16 | "cli-table3": "0.5.1", 17 | "commander": "3.0.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/typings/plugin/last-value/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ReportItem, Report } from '@/typings/command'; 2 | import { PluginConfig } from '@/typings/config/plugin'; 3 | 4 | export type InspectCallback = () => void | Promise; 5 | export type ItemFailReasons = string | false; 6 | 7 | export interface Config { 8 | failOnBreach: boolean; 9 | saveOnlyOnSuccess: boolean; 10 | storage?: PluginConfig; 11 | thresholds: { 12 | diffPercentage: number; 13 | number: number; 14 | percentage: number; 15 | size: number; 16 | }; 17 | } 18 | 19 | export type LastReportItem = { 20 | command: string; 21 | lastValue: number | string; 22 | lastValueChange?: number; 23 | lastValueDiff?: number; 24 | rawLastValue: number | string; 25 | report: string | Report; 26 | } & ReportItem; 27 | 28 | export interface GetEvent { 29 | command: string; 30 | } 31 | 32 | export interface SaveEvent { 33 | command: string; 34 | report: Report; 35 | } 36 | -------------------------------------------------------------------------------- /packages/typings/plugin/last-value/util.d.ts: -------------------------------------------------------------------------------- 1 | import { Meta } from '@/typings/module'; 2 | 3 | export interface DiffRet { 4 | change: number; 5 | diff: number; 6 | } 7 | 8 | export interface Metas { 9 | [label: string]: Meta; 10 | } 11 | -------------------------------------------------------------------------------- /packages/typings/tsconfig.json: -------------------------------------------------------------------------------- 1 | // the paths like outDir, rootDir, paths and include are 2 | // relative to this tsconfig.json 3 | { 4 | "extends": "../../tsconfig.json", 5 | "compilerOptions": { 6 | "outDir": "lib", 7 | "rootDir": ".", 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/typings/*": ["./*"], 11 | "@modus/gimbal-core": ["../gimbal-core/src/index.ts"], 12 | "@modus/gimbal-core/lib/*": ["../gimbal-core/src/*"] 13 | } 14 | }, 15 | "include": [ 16 | "./**/*" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/typings/utils/Queue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 2 | export type Data = any; 3 | 4 | export type Mode = 'sequential' | 'parallel'; 5 | -------------------------------------------------------------------------------- /packages/typings/utils/command.d.ts: -------------------------------------------------------------------------------- 1 | export interface CommandOptions { 2 | cwd: string; 3 | comment: boolean; 4 | config?: string; 5 | outputHtml?: string; 6 | outputJson?: string; 7 | outputMarkdown?: string; 8 | verbose: boolean; 9 | [name: string]: string | number | boolean | undefined; 10 | } 11 | 12 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 13 | export type GetOptionsFromCommand = (cmd?: any, defaults?: any) => CommandOptions; 14 | -------------------------------------------------------------------------------- /packages/typings/utils/env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable-next-line import/prefer-default-export,@typescript-eslint/no-explicit-any */ 2 | export type EnvOrDefault = (variableName: string, defaultValue?: any) => any; 3 | -------------------------------------------------------------------------------- /packages/typings/utils/fs.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable-next-line import/prefer-default-export */ 2 | export type ResolvePath = (...paths: string[]) => string; 3 | -------------------------------------------------------------------------------- /packages/typings/utils/spawn.d.ts: -------------------------------------------------------------------------------- 1 | import { StdioOptions } from 'child_process'; 2 | 3 | export interface CmdSpawnOptions { 4 | cwd?: string; 5 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 6 | env?: any; // cannot get ProcessEnv to work 7 | stdio?: StdioOptions; 8 | timeout?: number; 9 | } 10 | 11 | export interface CmdSpawnRet { 12 | code: number; 13 | end: Date; 14 | logs: Buffer[]; 15 | // TODO type os 16 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 17 | os?: any; 18 | start: Date; 19 | success: boolean; 20 | } 21 | -------------------------------------------------------------------------------- /packages/typings/utils/threshold.d.ts: -------------------------------------------------------------------------------- 1 | export interface LighthouseThreshold { 2 | accessibility?: number; 3 | 'best-practices'?: number; 4 | performance?: number; 5 | pwa?: number; 6 | seo?: number; 7 | [key: string]: number | void; 8 | } 9 | 10 | export interface AdvancedThreshold extends LighthouseThreshold { 11 | // heap-snapshot module 12 | Documents?: number; 13 | Frames?: number; 14 | JSHeapTotalSize?: number; 15 | JSHeapUsedSize?: number; 16 | LayoutCount?: number; 17 | Nodes?: number; 18 | RecalcStyleDuration?: number; 19 | 20 | [name: string]: number | undefined; 21 | } 22 | 23 | export type Threshold = number | AdvancedThreshold; 24 | 25 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 26 | export type Parser = (obj: any) => number; 27 | 28 | export type Modes = 'above' | 'below'; 29 | 30 | export interface Options { 31 | mode?: Modes; 32 | parser?: Parser; 33 | } 34 | -------------------------------------------------------------------------------- /packages/typings/vcs/GitHub/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IssuesCreateCommentParams, 3 | IssuesCreateCommentResponse, 4 | ReposCreateCommitCommentParams, 5 | ReposCreateCommitCommentResponse, 6 | Response, 7 | } from '@octokit/rest'; 8 | 9 | export interface CommitCommentStartEvent { 10 | comment: ReposCreateCommitCommentParams; 11 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 12 | vcs: any; // would cause circular dependency if imported the command class 13 | } 14 | 15 | export interface CommitCommentEndEvent { 16 | comment: ReposCreateCommitCommentParams; 17 | ret: Response; 18 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 19 | vcs: any; // would cause circular dependency if imported the command class 20 | } 21 | 22 | export interface CommitPRStartEvent { 23 | comment: IssuesCreateCommentParams; 24 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 25 | vcs: any; // would cause circular dependency if imported the command class 26 | } 27 | 28 | export interface CommitPREndEvent { 29 | comment: IssuesCreateCommentParams; 30 | ret: Response; 31 | /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ 32 | vcs: any; // would cause circular dependency if imported the command class 33 | } 34 | -------------------------------------------------------------------------------- /packages/typings/vcs/comment/index.d.ts: -------------------------------------------------------------------------------- 1 | import { CIs } from '@/ci'; 2 | import GitHub from '@/vcs/GitHub'; 3 | import { Report, ReportItem } from '@/typings/command'; 4 | import { CommandOptions } from '@/typings/utils/command'; 5 | 6 | export interface CommentBuildStartEvent { 7 | ci: CIs; 8 | report: Report; 9 | vcs: GitHub; 10 | } 11 | 12 | export interface CommentBuildEndEvent extends CommentBuildStartEvent { 13 | markdown: string; 14 | } 15 | 16 | export interface CommentRenderTableStartEvent { 17 | commandOptions: CommandOptions; 18 | reportItem: ReportItem; 19 | } 20 | 21 | export interface CommentRenderTableEndEvent extends CommentRenderTableStartEvent { 22 | renderedTable: string; 23 | } 24 | 25 | export interface CommentStartEvent { 26 | ci: CIs; 27 | comment: string; 28 | report: Report; 29 | vcs: GitHub; 30 | } 31 | 32 | export interface CommentEndEvent extends CommentStartEvent { 33 | comment: string; 34 | } 35 | 36 | export interface CommentObject { 37 | onlyFailures?: boolean; 38 | } 39 | 40 | export type Comment = boolean | CommentObject; 41 | -------------------------------------------------------------------------------- /packages/typings/vcs/index.d.ts: -------------------------------------------------------------------------------- 1 | import { URL } from 'url'; 2 | import GitHub from '@/vcs/GitHub'; 3 | 4 | export interface Cls { 5 | is: (url: URL) => boolean; 6 | name: string; 7 | } 8 | 9 | export type VCS = GitHub; 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "lib": ["es6", "es2015", "dom"], 7 | "declaration": true, 8 | "strict": true, 9 | "types": ["node", "jest"], 10 | "esModuleInterop": true, 11 | "resolveJsonModule": true, 12 | 13 | "plugins": [{ 14 | "transform": "@zerollup/ts-transform-paths", 15 | // these are the paths in the tsconfigs that should be ignored 16 | // they have to be hardcoded and exactly what is in the tsconfigs 17 | "exclude": [ 18 | "@modus/gimbal-core", 19 | "@modus/gimbal-core/lib/*" 20 | ] 21 | }], 22 | 23 | /* Strict Type-Checking Options */ 24 | /* Raise error on expressions and declarations with an implied 'any' type. */ 25 | "noImplicitAny": true, 26 | /* Enable strict null checks. */ 27 | "strictNullChecks": true, 28 | /* Enable strict checking of function types. */ 29 | "strictFunctionTypes": true, 30 | /* Enable strict checking of property initialization in classes. */ 31 | "strictPropertyInitialization": true, 32 | /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | "noImplicitThis": true, 34 | /* Parse in strict mode and emit "use strict" for each source file. */ 35 | "alwaysStrict": true, 36 | 37 | /* Additional Checks */ 38 | /* Report error when not all code paths in function return a value. */ 39 | "noImplicitReturns": true, 40 | /* Report errors for fallthrough cases in switch statement. */ 41 | "noFallthroughCasesInSwitch": true, 42 | 43 | /* Debugging Options */ 44 | /* Report module resolution log messages. */ 45 | "traceResolution": false, 46 | /* Print names of generated files part of the compilation. */ 47 | "listEmittedFiles": false, 48 | /* Print names of files part of the compilation. */ 49 | "listFiles": false, 50 | /* Stylize errors and messages using color and context. */ 51 | "pretty": true 52 | }, 53 | "exclude": [ 54 | "bin", 55 | "lib", 56 | "node_modules" 57 | ] 58 | } 59 | --------------------------------------------------------------------------------