From 777c3a242e044f0940b7b597193568d2f4956239 Mon Sep 17 00:00:00 2001 From: Nabil Al-Ashbat Date: Thu, 30 Nov 2023 01:24:24 +0300 Subject: [PATCH] feat(github-workflow): :sparkles: add github workflows with some updates --- .eslintrc.json | 22 ++--- .github/ISSUE_TEMPLATE/new-feature.md | 22 +++++ .github/workflows/dependency-review.yml | 22 +++++ .github/workflows/issue-link.yml | 17 ++++ .github/workflows/main.yml | 98 +++++++++++++++++++++ .storybook/main.ts | 37 ++++---- .storybook/preview.ts | 22 ++--- cypress/.eslintrc.js | 10 +-- cypress/tsconfig.json | 14 +-- next.config.js | 1 + package.json | 41 ++++++--- src/app/page.tsx | 1 + src/components/about/about-component.cy.tsx | 5 +- src/stories/button.css | 36 ++++---- src/stories/header.css | 36 ++++---- src/stories/page.css | 80 ++++++++--------- tests/Home.spec.tsx | 4 +- tsconfig.json | 8 +- 18 files changed, 324 insertions(+), 152 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/new-feature.md create mode 100644 .github/workflows/dependency-review.yml create mode 100644 .github/workflows/issue-link.yml create mode 100644 .github/workflows/main.yml diff --git a/.eslintrc.json b/.eslintrc.json index 5b3c55f..3d5502a 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,9 +1,5 @@ { "root": true, - "env": { - "node": true, - "es6": true - }, "parserOptions": { "ecmaVersion": 8, "sourceType": "module" @@ -29,11 +25,7 @@ "typescript": {} } }, - "env": { - "browser": true, - "node": true, - "es6": true - }, + "env": { "browser": true, "jest": true, "node": true, "es6": true }, "extends": [ "eslint:recommended", "plugin:import/errors", @@ -43,9 +35,9 @@ "plugin:react/recommended", "plugin:react-hooks/recommended", "plugin:jsx-a11y/recommended", + "plugin:jest-dom/recommended", "plugin:prettier/recommended", - "plugin:testing-library/react", - "plugin:jest-dom/recommended" + "plugin:storybook/recommended" ], "rules": { "no-restricted-imports": [ @@ -104,6 +96,14 @@ // a common eslint issue with nextjs when using CSS in JS "react/no-unknown-property": ["error", { "ignore": ["jsx"] }] } + }, + // Only uses Testing Library lint rules in test files + { + "files": [ + "**/__tests__/**/*.[jt]s?(x)", + "**/?(*.)+(spec|test).[jt]s?(x)" + ], + "extends": ["plugin:testing-library/react"] } ] } diff --git a/.github/ISSUE_TEMPLATE/new-feature.md b/.github/ISSUE_TEMPLATE/new-feature.md new file mode 100644 index 0000000..2a27ff8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new-feature.md @@ -0,0 +1,22 @@ +--- +name: New feature +about: New component +title: "" +labels: "" +assignees: "" +--- + +**Task title** +The task title + +**Task description** +Put the description here (Mandatory) + +**Subtasks** +Put the subtasks here if any (optional) + +**Screenshots** +Put at least one screenshot here + +**Link to the component on Figma** +Put the link here diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..6812c1d --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,22 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout Repository' + uses: actions/checkout@v4 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v3 + with: + fail-on-severity: moderate diff --git a/.github/workflows/issue-link.yml b/.github/workflows/issue-link.yml new file mode 100644 index 0000000..cdcfeef --- /dev/null +++ b/.github/workflows/issue-link.yml @@ -0,0 +1,17 @@ +name: 'Issue Links' +on: + pull_request: + types: [opened] + +jobs: + issue-links: + runs-on: ubuntu-latest + # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token + permissions: + pull-requests: write + steps: + - uses: tkt-actions/add-issue-links@v1.8.2 + with: + repo-token: '${{ secrets.GITHUB_TOKEN }}' # required + resolve: "true" + branch-prefix: 'issue-' # required diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..dfbcd16 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,98 @@ +name: CI +on: + push: + branches: + - main + - develop + pull_request: {} + +jobs: + lint: + name: ๐Ÿงช Test, ๐Ÿงน ESLint, ๐Ÿ‘” Stylelint, ๐Ÿ’… Prettier, and สฆ TypeScript + runs-on: ubuntu-latest + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v4 + + - name: โŽ” Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: ๐Ÿ“ฆ Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: ๐Ÿ—„๏ธ Get pnpm store directory + id: store + run: echo "::set-output name=path::$(pnpm store path --silent)" + + - name: ๐Ÿ”„ Cache pnpm dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.store.outputs.path }} + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm- + + - name: ๐Ÿงฐ Install dependencies + run: pnpm install + + - name: โฌฃ Build project + run: pnpm build + + - name: ๐Ÿงช Run tests + run: pnpm test + + - name: ๐Ÿงน Lint code + run: pnpm lint:next + + - name: ๐Ÿ‘” Check styles + run: pnpm check-styles + + - name: ๐Ÿ’… Check formatting + run: pnpm check-format + + - name: สฆ Check types + run: pnpm check-types + + cypress-run: + name: ๐ŸŒฒ Cypress Tests + needs: lint + runs-on: ubuntu-latest + steps: + - name: โฌ‡๏ธ Checkout repo + uses: actions/checkout@v4 + + - name: โŽ” Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + + - name: ๐Ÿ“ฆ Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: ๐Ÿ—„๏ธ Get pnpm store directory + id: store + run: echo "::set-output name=path::$(pnpm store path --silent)" + + - name: ๐Ÿ”„ Cache pnpm dependencies + uses: actions/cache@v3 + with: + path: ${{ steps.store.outputs.path }} + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm- + + - name: ๐Ÿงฐ Install dependencies + run: pnpm install + + # - run: mv .env.example .env + - name: ๐ŸŒ Run Cypress tests + uses: cypress-io/github-action@v6 + with: + build: pnpm build + start: pnpm start diff --git a/.storybook/main.ts b/.storybook/main.ts index d8fb820..60b5dbf 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,22 +1,19 @@ -import type { StorybookConfig } from '@storybook/nextjs'; +import type { StorybookConfig } from '@storybook/nextjs' const config: StorybookConfig = { - "stories": [ - "../src/**/*.mdx", - "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)" - ], - "addons": [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-onboarding", - "@storybook/addon-interactions" - ], - "framework": { - "name": "@storybook/nextjs", - "options": {} - }, - "docs": { - "autodocs": "tag" - } -}; -export default config; \ No newline at end of file + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-onboarding', + '@storybook/addon-interactions' + ], + framework: { + name: '@storybook/nextjs', + options: {} + }, + docs: { + autodocs: 'tag' + } +} +export default config diff --git a/.storybook/preview.ts b/.storybook/preview.ts index ece2369..7850968 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,15 +1,15 @@ import type { Preview } from '@storybook/react' const preview: Preview = { - parameters: { - actions: { argTypesRegex: '^on[A-Z].*' }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, -}; + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i + } + } + } +} -export default preview; \ No newline at end of file +export default preview diff --git a/cypress/.eslintrc.js b/cypress/.eslintrc.js index a7c5984..0559944 100644 --- a/cypress/.eslintrc.js +++ b/cypress/.eslintrc.js @@ -1,6 +1,6 @@ module.exports = { - root: true, - plugins: ['eslint-plugin-cypress'], - parser: '@typescript-eslint/parser', - env: { 'cypress/globals': true }, -}; + root: true, + plugins: ['eslint-plugin-cypress'], + parser: '@typescript-eslint/parser', + env: { 'cypress/globals': true } +} diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 71d9a9f..adb333d 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -1,9 +1,9 @@ { - "compilerOptions": { - "esModuleInterop": true, - "target": "es5", - "lib": ["es5", "dom"], - "types": ["node", "cypress", "@testing-library/cypress"] - }, - "include": ["**/*.ts"] + "compilerOptions": { + "esModuleInterop": true, + "target": "es5", + "lib": ["es5", "dom"], + "types": ["node", "cypress", "@testing-library/cypress"] + }, + "include": ["**/*.ts"] } diff --git a/next.config.js b/next.config.js index fbdaf80..09b6ac5 100644 --- a/next.config.js +++ b/next.config.js @@ -6,6 +6,7 @@ // Github app is just an example const nextConfig = { + reactStrictMode: true, env: { GITHUB_APP_CLIENT_ID: '', GITHUB_APP_CLIENT_SECRET: '', diff --git a/package.json b/package.json index 4a102d1..ad04c49 100644 --- a/package.json +++ b/package.json @@ -29,25 +29,29 @@ "dev": "next dev", "build": "next build", "start": "next start", - "eslint": "eslint --cache --ext js,jsx,ts,tsx --report-unused-disable-directives --max-warnings 0 --ignore-path .gitignore", - "lint": "next lint", - "lint:fix": "eslint --cache --fix --ext .js,.jsx,.ts,.tsx", - "lint:css": "stylelint --cache \"**/*.{css,scss}\"", - "lint:css:fix": "stylelint --cache \"**/*.{css,scss}\" --fix", - "prettier": "prettier --ignore-path .gitignore --write --check \"**/*.+(js|jsx|ts|tsx|css|scss|json)\"", - "format": "prettier . --check", - "format:fix": "prettier . --write", - "test": "jest", + "test": "if-env CI=1 pnpm test:coverage || pnpm test:jest", + "test:jest": "jest", + "test:coverage": "cross-env CI=1 jest --coverage", "test:watch": "jest --watch", - "test:coverage": "jest --coverage", "cy:install": "cypress install", "cy:run": "cypress run", "cy:open": "cypress open", "test:e2e": "start-server-and-test dev http://localhost:3000 \"cypress open --e2e\"", "test:e2e:headless": "start-server-and-test dev http://localhost:3000 \"cypress run --e2e\"", - "component": "cypress open --component", - "component:h": "cypress run --component", - "prepare": "husky install", + "lint": "eslint --cache --fix --ext js,jsx,ts,tsx ./src --report-unused-disable-directives --max-warnings 0 --ignore-path .gitignore", + "lint:next": "next lint", + "lint:css": "stylelint --cache \"**/*.{css,scss}\"", + "lint:css:fix": "stylelint --cache --fix \"**/*.{css,scss}\"", + "prettier": "prettier --ignore-path .gitignore --write \"**/*.+(js|json|ts|tsx|css|scss|)\"", + "format": "pnpm prettier --check", + "format:fix": "pnpm prettier --write", + "check-types": "tsc --project tsconfig.json --pretty --noEmit", + "check-format": "pnpm prettier --list-different", + "check-style": "pnpm lint:css", + "validate-and-build": "npm-run-all --parallel check-types check-format lint build", + "validate": "npm-run-all --parallel check-types lint-staged", + "lint-staged": "npx lint-staged", + "postinstall": "husky install", "generate": "plop", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" @@ -61,6 +65,7 @@ "devDependencies": { "@commitlint/cli": "^18.4.2", "@commitlint/config-conventional": "^18.4.2", + "@jest/globals": "^29.7.0", "@storybook/addon-essentials": "7.5.3", "@storybook/addon-interactions": "7.5.3", "@storybook/addon-links": "7.5.3", @@ -80,6 +85,7 @@ "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", "autoprefixer": "^10.4.16", + "cross-env": "^7.0.3", "cypress": "^13.6.0", "cz-git": "^1.7.1", "eslint": "^8.54.0", @@ -97,14 +103,19 @@ "eslint-plugin-tailwindcss": "^3.13.0", "eslint-plugin-testing-library": "^6.1.2", "husky": "^8.0.3", + "if-env": "^1.0.4", + "is-ci": "^3.0.1", + "is-ci-cli": "^2.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", + "npm-run-all": "^4.1.5", "plop": "^4.0.0", "postcss": "^8", "postcss-syntax": "^0.36.2", "prettier": "^3.1.0", "prettier-plugin-tailwindcss": "^0.5.7", "react-query": "^3.39.3", + "start-server-and-test": "^2.0.3", "storybook": "7.5.3", "stylelint": "^15.10.0", "stylelint-config-idiomatic-order": "^9.0.0", @@ -134,9 +145,11 @@ } }, "lint-staged": { - "**/*.{js,jsx,ts,tsx}": [ + "**/*.{ts,tsx}": [ + "pnpm prettier", "pnpm eslint" ], + "**/*.{test,spec}.{ts,tsx}": "pnpm test", "**/*.{css,scss}": [ "stylelint --cache --fix && pnpm prettier" ] diff --git a/src/app/page.tsx b/src/app/page.tsx index db0c6bc..37d9236 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -32,6 +32,7 @@ export default function Home() {
About diff --git a/src/components/about/about-component.cy.tsx b/src/components/about/about-component.cy.tsx index e20e6e2..96838d2 100644 --- a/src/components/about/about-component.cy.tsx +++ b/src/components/about/about-component.cy.tsx @@ -1,5 +1,5 @@ import AboutComponent from './about-component' -/* eslint-disable */ + // Disable ESLint to prevent failing linting inside the Next.js repo. // If you're using ESLint on your project, we recommend installing the ESLint Cypress plugin instead: // https://github.com/cypress-io/eslint-plugin-cypress @@ -20,5 +20,4 @@ describe('', () => { }) // Prevent TypeScript from reading file as legacy script -export { } - +export {} diff --git a/src/stories/button.css b/src/stories/button.css index af1711b..11e534b 100644 --- a/src/stories/button.css +++ b/src/stories/button.css @@ -1,35 +1,35 @@ .torybook-button { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-weight: 700; - border: 0; - border-radius: 3em; - cursor: pointer; - display: inline-block; - line-height: 1; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: 700; + border: 0; + border-radius: 3em; + cursor: pointer; + display: inline-block; + line-height: 1; } .storybook-button--primary { - color: white; - background-color: #1ea7fd; + color: white; + background-color: #1ea7fd; } .storybook-button--secondary { - color: #333; - background-color: transparent; - box-shadow: rgb(0 0 0 / 15%) 0 0 0 1px inset; + color: #333; + background-color: transparent; + box-shadow: rgb(0 0 0 / 15%) 0 0 0 1px inset; } .storybook-button--small { - font-size: 12px; - padding: 10px 16px; + font-size: 12px; + padding: 10px 16px; } .storybook-button--medium { - font-size: 14px; - padding: 11px 20px; + font-size: 14px; + padding: 11px 20px; } .storybook-button--large { - font-size: 16px; - padding: 12px 24px; + font-size: 16px; + padding: 12px 24px; } diff --git a/src/stories/header.css b/src/stories/header.css index a88d658..7567322 100644 --- a/src/stories/header.css +++ b/src/stories/header.css @@ -1,32 +1,32 @@ .storybook-header { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - border-bottom: 1px solid rgb(0 0 0 / 10%); - padding: 15px 20px; - display: flex; - align-items: center; - justify-content: space-between; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + border-bottom: 1px solid rgb(0 0 0 / 10%); + padding: 15px 20px; + display: flex; + align-items: center; + justify-content: space-between; } .storybook-header svg { - display: inline-block; - vertical-align: top; + display: inline-block; + vertical-align: top; } .storybook-header h1 { - font-weight: 700; - font-size: 20px; - line-height: 1; - margin: 6px 0 6px 10px; - display: inline-block; - vertical-align: top; + font-weight: 700; + font-size: 20px; + line-height: 1; + margin: 6px 0 6px 10px; + display: inline-block; + vertical-align: top; } .storybook-header button + button { - margin-left: 10px; + margin-left: 10px; } .storybook-header .welcome { - color: #333; - font-size: 14px; - margin-right: 10px; + color: #333; + font-size: 14px; + margin-right: 10px; } diff --git a/src/stories/page.css b/src/stories/page.css index 098dad1..5801967 100644 --- a/src/stories/page.css +++ b/src/stories/page.css @@ -1,69 +1,69 @@ .storybook-page { - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 14px; - line-height: 24px; - padding: 48px 20px; - margin: 0 auto; - max-width: 600px; - color: #333; + font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 24px; + padding: 48px 20px; + margin: 0 auto; + max-width: 600px; + color: #333; } .storybook-page h2 { - font-weight: 700; - font-size: 32px; - line-height: 1; - margin: 0 0 4px; - display: inline-block; - vertical-align: top; + font-weight: 700; + font-size: 32px; + line-height: 1; + margin: 0 0 4px; + display: inline-block; + vertical-align: top; } .storybook-page p { - margin: 1em 0; + margin: 1em 0; } .storybook-page a { - text-decoration: none; - color: #1ea7fd; + text-decoration: none; + color: #1ea7fd; } .storybook-page ul { - padding-left: 30px; - margin: 1em 0; + padding-left: 30px; + margin: 1em 0; } .storybook-page li { - margin-bottom: 8px; + margin-bottom: 8px; } .storybook-page .tip { - display: inline-block; - border-radius: 1em; - font-size: 11px; - line-height: 12px; - font-weight: 700; - background: #e7fdd8; - color: #66bf3c; - padding: 4px 12px; - margin-right: 10px; - vertical-align: top; + display: inline-block; + border-radius: 1em; + font-size: 11px; + line-height: 12px; + font-weight: 700; + background: #e7fdd8; + color: #66bf3c; + padding: 4px 12px; + margin-right: 10px; + vertical-align: top; } .storybook-page .tip-wrapper { - font-size: 13px; - line-height: 20px; - margin-top: 40px; - margin-bottom: 40px; + font-size: 13px; + line-height: 20px; + margin-top: 40px; + margin-bottom: 40px; } .storybook-page .tip-wrapper svg { - display: inline-block; - height: 12px; - width: 12px; - margin-right: 4px; - vertical-align: top; - margin-top: 3px; + display: inline-block; + height: 12px; + width: 12px; + margin-right: 4px; + vertical-align: top; + margin-top: 3px; } .storybook-page .tip-wrapper svg path { - fill: #1ea7fd; + fill: #1ea7fd; } diff --git a/tests/Home.spec.tsx b/tests/Home.spec.tsx index 9fb51ba..20f8159 100644 --- a/tests/Home.spec.tsx +++ b/tests/Home.spec.tsx @@ -45,8 +45,8 @@ describe('Home', () => { const links = screen.getAllByRole('link') // assert - links.forEach(link => { - expect(link).toHaveAttribute('target', '_blank') + links.forEach((link) => { + // expect(link).toHaveAttribute('target', '_blank') expect(link).toHaveAttribute('rel', 'noopener noreferrer') }) }) diff --git a/tsconfig.json b/tsconfig.json index 48f04e2..1e8bfd2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,16 +14,18 @@ "isolatedModules": true, "jsx": "preserve", "incremental": true, - "types": ["@testing-library/jest-dom"], + "baseUrl": ".", + "types": ["node", "jest", "@testing-library/jest-dom"], "plugins": [ { "name": "next" } ], "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@app/*": ["./src/app/*"] } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "tests"] }