├── .all-contributorsrc ├── .eslintrc.js ├── .gitconfig ├── .github ├── actions │ ├── cache-node-modules │ │ └── action.yml │ ├── cache-package-builds │ │ └── action.yml │ ├── cache-project-dependencies │ │ └── action.yml │ ├── get-project-changes │ │ └── action.yml │ ├── install-node-deps │ │ └── action.yml │ ├── lint-and-tests │ │ └── action.yml │ └── package │ │ └── action.yml └── workflows │ └── pr.yml ├── .gitignore ├── .husky ├── commit-msg ├── post-merge └── pre-commit ├── .lintstagedrc.js ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .syncpackrc.js ├── .yarn ├── plugins │ └── @yarnpkg │ │ ├── plugin-interactive-tools.cjs │ │ └── plugin-workspace-tools.cjs └── releases │ └── yarn-3.2.1.cjs ├── .yarnrc.yml ├── LICENSE.md ├── README.md ├── backend ├── core │ ├── .dependency-cruiser.js │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── functions │ │ ├── health │ │ │ ├── config.ts │ │ │ ├── handler.mock.json │ │ │ └── handler.ts │ │ └── index.ts │ ├── package.json │ ├── project.json │ ├── serverless.test.ts │ ├── serverless.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── forum │ ├── .dependency-cruiser.js │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── functions │ │ ├── createPost │ │ │ ├── config.ts │ │ │ ├── handler.mock.json │ │ │ ├── handler.test.ts │ │ │ └── handler.ts │ │ ├── getThreadAndPosts │ │ │ ├── config.ts │ │ │ ├── handler.mock.json │ │ │ └── handler.ts │ │ └── index.ts │ ├── package.json │ ├── project.json │ ├── serverless.test.ts │ ├── serverless.ts │ ├── tsconfig.json │ └── vitest.config.ts └── users │ ├── .dependency-cruiser.js │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── functions │ ├── getUser │ │ ├── config.ts │ │ ├── handler.mock.json │ │ └── handler.ts │ └── index.ts │ ├── package.json │ ├── project.json │ ├── serverless.test.ts │ ├── serverless.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── commitlint.config.js ├── commonConfiguration ├── .vscode │ └── launch.json ├── babel.config.js └── dependency-cruiser.config.js ├── contracts ├── core-contracts │ ├── .dependency-cruiser.js │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── babel.config.js │ ├── package.json │ ├── project.json │ ├── src │ │ ├── contracts │ │ │ ├── cloudFormation.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vitest.config.ts ├── forum-contracts │ ├── .dependency-cruiser.js │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── babel.config.js │ ├── package.json │ ├── project.json │ ├── src │ │ ├── contracts │ │ │ ├── entities │ │ │ │ ├── index.ts │ │ │ │ ├── post.ts │ │ │ │ └── thread.ts │ │ │ ├── index.ts │ │ │ └── requests │ │ │ │ ├── createPost.ts │ │ │ │ ├── getThreadWithPosts.ts │ │ │ │ └── index.ts │ │ └── index.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vitest.config.ts └── users-contracts │ ├── .dependency-cruiser.js │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── babel.config.js │ ├── package.json │ ├── project.json │ ├── src │ ├── contracts │ │ ├── entities │ │ │ ├── index.ts │ │ │ └── user.ts │ │ ├── index.ts │ │ └── requests │ │ │ ├── getUser.ts │ │ │ └── index.ts │ └── index.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vitest.config.ts ├── fresko.config.ts ├── frontend ├── .eslintrc.js ├── app │ ├── .dependency-cruiser.js │ ├── .env.example │ ├── .env.production │ ├── .env.staging │ ├── .env.test │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .lintstagedrc.js │ ├── .stylelintrc.js │ ├── .vscode │ ├── config │ │ ├── checkEnvConsistency.ts │ │ └── index.ts │ ├── global.d.ts │ ├── index.html │ ├── package.json │ ├── project.json │ ├── src │ │ ├── App.tsx │ │ ├── AppRoutes.tsx │ │ ├── __fixtures__ │ │ │ ├── state.ts │ │ │ └── user.ts │ │ ├── __mocks__ │ │ │ └── svgrMock.ts │ │ ├── components │ │ │ ├── Title │ │ │ │ ├── Title.tsx │ │ │ │ ├── __tests__ │ │ │ │ │ └── Title.test.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── env.d.ts │ │ ├── favicon.svg │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── Home │ │ │ │ ├── Home.style.ts │ │ │ │ ├── Home.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── services │ │ │ ├── i18n │ │ │ │ ├── flattenMessages.ts │ │ │ │ └── index.ts │ │ │ └── networking │ │ │ │ └── client.ts │ │ ├── store │ │ │ ├── configureStore.ts │ │ │ ├── reducers.ts │ │ │ └── user │ │ │ │ ├── __tests__ │ │ │ │ ├── reducer.test.ts │ │ │ │ └── selector.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── selectors.ts │ │ │ │ └── slice.ts │ │ ├── testUtils │ │ │ ├── WrapperForLocationTesting │ │ │ │ ├── WrapperForLocationTesting.tsx │ │ │ │ └── index.ts │ │ │ ├── WrapperForReduxTesting │ │ │ │ ├── WrapperForReduxTesting.tsx │ │ │ │ └── index.ts │ │ │ ├── react-hooks.tsx │ │ │ └── react.tsx │ │ ├── theme │ │ │ ├── helpers │ │ │ │ ├── index.ts │ │ │ │ └── spacing.ts │ │ │ ├── index.ts │ │ │ ├── muiTheme.ts │ │ │ ├── overrides │ │ │ │ ├── MuiButton │ │ │ │ │ ├── MuiButtonOverrides.ts │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── styled.ts │ │ │ └── variants │ │ │ │ ├── MuiTypography │ │ │ │ ├── getH1Variants.ts │ │ │ │ ├── getH2Variants.ts │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ ├── translations │ │ │ ├── fr-FR.ts │ │ │ └── index.ts │ │ └── types │ │ │ ├── RoutePaths.ts │ │ │ └── index.ts │ ├── tsconfig.json │ ├── vite.config.ts │ └── vitest.setup.ts ├── cloudfront │ ├── .dependency-cruiser.js │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── deploy.sh │ ├── package.json │ ├── project.json │ ├── serverless.ts │ └── tsconfig.json └── shared │ ├── .dependency-cruiser.js │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── babel.config.js │ ├── global.d.ts │ ├── package.json │ ├── project.json │ ├── src │ ├── components │ │ ├── NotFound │ │ │ ├── NotFound.tsx │ │ │ ├── __tests__ │ │ │ │ └── NotFound.test.tsx │ │ │ └── index.ts │ │ └── index.ts │ └── index.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── vitest.config.ts │ └── vitest.setup.ts ├── get-affected-paths.sh ├── nx.json ├── package.json ├── packages └── serverless-configuration │ ├── .dependency-cruiser.js │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .lintstagedrc.js │ ├── .vscode │ ├── babel.config.js │ ├── package.json │ ├── project.json │ ├── src │ ├── index.ts │ └── sharedConfig.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vitest.config.ts ├── swarmion-starter.code-workspace ├── tsconfig.json ├── workspace.json └── yarn.lock /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "fargito", 10 | "name": "François Farge", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/29537204?v=4", 12 | "profile": "https://github.com/fargito", 13 | "contributions": [ 14 | "infra", 15 | "code", 16 | "ideas", 17 | "doc" 18 | ] 19 | }, 20 | { 21 | "login": "adriencaccia", 22 | "name": "Adrien Cacciaguerra", 23 | "avatar_url": "https://avatars.githubusercontent.com/u/19605940?v=4", 24 | "profile": "https://github.com/adriencaccia", 25 | "contributions": [ 26 | "code", 27 | "ideas", 28 | "infra" 29 | ] 30 | }, 31 | { 32 | "login": "LouisPinsard", 33 | "name": "Louis Pinsard", 34 | "avatar_url": "https://avatars.githubusercontent.com/u/30240360?v=4", 35 | "profile": "https://github.com/LouisPinsard", 36 | "contributions": [ 37 | "code" 38 | ] 39 | }, 40 | { 41 | "login": "guillaumeduboc", 42 | "name": "guillaumeduboc", 43 | "avatar_url": "https://avatars.githubusercontent.com/u/33599414?v=4", 44 | "profile": "https://github.com/guillaumeduboc", 45 | "contributions": [ 46 | "code" 47 | ] 48 | }, 49 | { 50 | "login": "MaximeVivier", 51 | "name": "Maxime Vivier", 52 | "avatar_url": "https://avatars.githubusercontent.com/u/55386175?v=4", 53 | "profile": "https://github.com/MaximeVivier", 54 | "contributions": [ 55 | "code" 56 | ] 57 | }, 58 | { 59 | "login": "GuillaumeLagrange", 60 | "name": "Guillaume Lagrange", 61 | "avatar_url": "https://avatars.githubusercontent.com/u/19265358?v=4", 62 | "profile": "https://github.com/GuillaumeLagrange", 63 | "contributions": [ 64 | "code" 65 | ] 66 | }, 67 | { 68 | "login": "pmilliotte", 69 | "name": "Pierre Milliotte", 70 | "avatar_url": "https://avatars.githubusercontent.com/u/39985796?v=4", 71 | "profile": "https://github.com/pmilliotte", 72 | "contributions": [ 73 | "code" 74 | ] 75 | }, 76 | { 77 | "login": "ThomasAribart", 78 | "name": "Thomas Aribart", 79 | "avatar_url": "https://avatars.githubusercontent.com/u/38014240?v=4", 80 | "profile": "https://github.com/ThomasAribart", 81 | "contributions": [ 82 | "doc" 83 | ] 84 | }, 85 | { 86 | "login": "charlesgery", 87 | "name": "Charles Géry", 88 | "avatar_url": "https://avatars.githubusercontent.com/u/46850903?v=4", 89 | "profile": "https://github.com/charlesgery", 90 | "contributions": [ 91 | "code" 92 | ] 93 | }, 94 | { 95 | "login": "StanHannebelle", 96 | "name": "Stan Hannebelle", 97 | "avatar_url": "https://avatars.githubusercontent.com/u/45121661?v=4", 98 | "profile": "https://github.com/StanHannebelle", 99 | "contributions": [ 100 | "code" 101 | ] 102 | }, 103 | { 104 | "login": "qhello", 105 | "name": "Quentin Hello", 106 | "avatar_url": "https://avatars.githubusercontent.com/u/9997584?v=4", 107 | "profile": "https://github.com/qhello", 108 | "contributions": [ 109 | "infra" 110 | ] 111 | }, 112 | { 113 | "login": "Paulmolin", 114 | "name": "Paul Molin", 115 | "avatar_url": "https://avatars.githubusercontent.com/u/5166068?v=4", 116 | "profile": "https://github.com/Paulmolin", 117 | "contributions": [ 118 | "code" 119 | ] 120 | } 121 | ], 122 | "contributorsPerLine": 7, 123 | "projectName": "template", 124 | "projectOwner": "swarmion", 125 | "repoType": "github", 126 | "repoHost": "https://github.com", 127 | "skipCi": true 128 | } 129 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'eslint:recommended', 4 | 'plugin:import/recommended', 5 | 'plugin:prettier/recommended', 6 | ], 7 | rules: { 8 | 'prettier/prettier': 'error', 9 | 'import/extensions': 0, 10 | 'import/no-unresolved': 0, 11 | 'import/prefer-default-export': 0, 12 | 'import/no-duplicates': 'error', 13 | complexity: ['error', 8], 14 | 'max-lines': ['error', 200], 15 | 'max-depth': ['error', 3], 16 | 'max-params': ['error', 4], 17 | eqeqeq: ['error', 'smart'], 18 | 'import/no-extraneous-dependencies': [ 19 | 'error', 20 | { 21 | devDependencies: true, 22 | optionalDependencies: false, 23 | peerDependencies: false, 24 | }, 25 | ], 26 | 'no-shadow': [ 27 | 'error', 28 | { 29 | hoist: 'all', 30 | }, 31 | ], 32 | 'prefer-const': 'error', 33 | 'import/order': [ 34 | 'error', 35 | { 36 | pathGroups: [{ pattern: '@swarmion-starter/**', group: 'unknown' }], 37 | groups: [ 38 | ['external', 'builtin'], 39 | 'unknown', 40 | 'internal', 41 | ['parent', 'sibling', 'index'], 42 | ], 43 | alphabetize: { 44 | order: 'asc', 45 | caseInsensitive: false, 46 | }, 47 | 'newlines-between': 'always', 48 | pathGroupsExcludedImportTypes: ['builtin'], 49 | }, 50 | ], 51 | 'sort-imports': [ 52 | 'error', 53 | { 54 | ignoreCase: true, 55 | ignoreDeclarationSort: true, 56 | ignoreMemberSort: false, 57 | memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'], 58 | }, 59 | ], 60 | 'padding-line-between-statements': [ 61 | 'error', 62 | { 63 | blankLine: 'always', 64 | prev: '*', 65 | next: 'return', 66 | }, 67 | ], 68 | 'prefer-arrow/prefer-arrow-functions': [ 69 | 'error', 70 | { 71 | disallowPrototype: true, 72 | singleReturnOnly: false, 73 | classPropertiesAllowed: false, 74 | }, 75 | ], 76 | 'no-restricted-imports': [ 77 | 'error', 78 | { 79 | patterns: [ 80 | { 81 | group: ['@swarmion-starter/*/*'], 82 | message: 83 | 'import of internal modules must be done at the root level.', 84 | }, 85 | ], 86 | paths: [ 87 | { 88 | name: 'lodash', 89 | message: 'Please use lodash/{module} import instead', 90 | }, 91 | { 92 | name: 'aws-sdk', 93 | message: 'Please use aws-sdk/{module} import instead', 94 | }, 95 | { 96 | name: '.', 97 | message: 'Please use explicit import file', 98 | }, 99 | ], 100 | }, 101 | ], 102 | curly: ['error', 'all'], 103 | }, 104 | root: true, 105 | env: { 106 | es6: true, 107 | node: true, 108 | jest: true, 109 | browser: true, 110 | }, 111 | plugins: ['prefer-arrow', 'import'], 112 | parserOptions: { 113 | ecmaVersion: 9, 114 | sourceType: 'module', 115 | }, 116 | overrides: [ 117 | { 118 | files: ['**/*.ts?(x)'], 119 | extends: [ 120 | 'plugin:@typescript-eslint/recommended', 121 | 'plugin:@typescript-eslint/recommended-requiring-type-checking', 122 | 'plugin:prettier/recommended', 123 | ], 124 | parser: '@typescript-eslint/parser', 125 | parserOptions: { 126 | project: 'tsconfig.json', 127 | }, 128 | settings: { 'import/resolver': { typescript: {} } }, 129 | rules: { 130 | '@typescript-eslint/prefer-optional-chain': 'error', 131 | 'no-shadow': 'off', 132 | '@typescript-eslint/no-shadow': 'error', 133 | '@typescript-eslint/prefer-nullish-coalescing': 'error', 134 | '@typescript-eslint/strict-boolean-expressions': [ 135 | 'error', 136 | { 137 | allowString: false, 138 | allowNumber: false, 139 | allowNullableObject: true, 140 | }, 141 | ], 142 | '@typescript-eslint/ban-ts-comment': [ 143 | 'error', 144 | { 145 | 'ts-ignore': 'allow-with-description', 146 | minimumDescriptionLength: 10, 147 | }, 148 | ], 149 | '@typescript-eslint/explicit-function-return-type': 0, 150 | '@typescript-eslint/explicit-member-accessibility': 0, 151 | '@typescript-eslint/camelcase': 0, 152 | '@typescript-eslint/interface-name-prefix': 0, 153 | '@typescript-eslint/explicit-module-boundary-types': 'error', 154 | '@typescript-eslint/no-explicit-any': 'error', 155 | '@typescript-eslint/no-unused-vars': 'error', 156 | '@typescript-eslint/ban-types': [ 157 | 'error', 158 | { 159 | types: { 160 | FC: 'Use `const MyComponent = (props: Props): JSX.Element` instead', 161 | SFC: 'Use `const MyComponent = (props: Props): JSX.Element` instead', 162 | FunctionComponent: 163 | 'Use `const MyComponent = (props: Props): JSX.Element` instead', 164 | 'React.FC': 165 | 'Use `const MyComponent = (props: Props): JSX.Element` instead', 166 | 'React.SFC': 167 | 'Use `const MyComponent = (props: Props): JSX.Element` instead', 168 | 'React.FunctionComponent': 169 | 'Use `const MyComponent = (props: Props): JSX.Element` instead', 170 | }, 171 | extendDefaults: true, 172 | }, 173 | ], 174 | '@typescript-eslint/no-unnecessary-boolean-literal-compare': 'error', 175 | '@typescript-eslint/no-unnecessary-condition': 'error', 176 | '@typescript-eslint/no-unnecessary-type-arguments': 'error', 177 | '@typescript-eslint/prefer-string-starts-ends-with': 'error', 178 | '@typescript-eslint/switch-exhaustiveness-check': 'error', 179 | '@typescript-eslint/restrict-template-expressions': [ 180 | 'error', 181 | { 182 | allowNumber: true, 183 | allowBoolean: true, 184 | }, 185 | ], 186 | }, 187 | }, 188 | ], 189 | }; 190 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [pull] 2 | rebase = true 3 | -------------------------------------------------------------------------------- /.github/actions/cache-node-modules/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache node modules' 2 | description: 'Cache node modules' 3 | outputs: 4 | cache-hit: 5 | description: 'True if cache was hit, false otherwise' 6 | value: ${{ steps.cache-node-modules.outputs.cache-hit }} 7 | runs: 8 | using: 'composite' 9 | steps: 10 | - name: Cache node_modules 11 | id: cache-node-modules 12 | uses: actions/cache@v2 13 | with: 14 | path: '**/node_modules' 15 | key: ${{ runner.os }}-modules-${{ env.NODE_VERSION }}-${{ hashFiles('./yarn.lock') }} 16 | -------------------------------------------------------------------------------- /.github/actions/cache-package-builds/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache package builds' 2 | description: 'Cache package builds' 3 | outputs: 4 | cache-hit: 5 | description: 'True if cache was hit, false otherwise' 6 | value: ${{ steps.package-cache.outputs.cache-hit }} 7 | runs: 8 | using: 'composite' 9 | steps: 10 | - name: Cache package builds 11 | id: package-cache 12 | uses: actions/cache@v2 13 | with: 14 | path: | 15 | node_modules/@swarmion-starter/**/dist 16 | key: ${{ hashFiles('./contracts/**') }}-${{ hashFiles('./packages/**') }}-${{ hashFiles('./frontend/shared/**') }} 17 | -------------------------------------------------------------------------------- /.github/actions/cache-project-dependencies/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache project dependencies' 2 | description: 'Cache both node modules and library builds' 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - name: Cache node_modules 7 | uses: ./.github/actions/cache-node-modules 8 | - name: Cache package builds 9 | uses: ./.github/actions/cache-package-builds 10 | -------------------------------------------------------------------------------- /.github/actions/get-project-changes/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Get Project Code Changes' 2 | description: 'Returns the paths of apps and libraries have been modified in a PR' 3 | outputs: 4 | affected_apps: 5 | description: Paths of the affected apps 6 | value: ${{ steps.affected-apps.outputs.changes }} 7 | affected_libs: 8 | description: Paths of the affected libs 9 | value: ${{ steps.affected-libs.outputs.changes }} 10 | runs: 11 | using: 'composite' 12 | steps: 13 | - name: Derive appropriate SHAs for base and head for `nx affected` commands 14 | uses: nrwl/nx-set-shas@v2 15 | - name: Get affected apps 16 | id: affected-apps 17 | run: | 18 | changes="$(./get-affected-paths.sh apps)" 19 | echo "::set-output name=changes::$changes" 20 | shell: bash 21 | - name: Get affected libs 22 | id: affected-libs 23 | run: | 24 | changes="$(./get-affected-paths.sh libs)" 25 | echo "::set-output name=changes::$changes" 26 | shell: bash 27 | -------------------------------------------------------------------------------- /.github/actions/install-node-deps/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Install Node Dependencies' 2 | description: 'Install monorepo dependencies using yarn' 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - name: Use Node.js 7 | uses: actions/setup-node@v2 8 | with: 9 | node-version: ${{ env.NODE_VERSION }} 10 | - name: Get yarn cache directory path 11 | id: yarn-cache-dir-path 12 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 13 | shell: bash 14 | - name: Cache yarn cache 15 | uses: actions/cache@v2 16 | with: 17 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 18 | key: ${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }} 19 | restore-keys: | 20 | ${{ runner.os }}-yarn- 21 | - name: Cache node modules 22 | uses: ./.github/actions/cache-node-modules 23 | - name: Install dependencies 24 | run: yarn install --immutable 25 | shell: bash 26 | -------------------------------------------------------------------------------- /.github/actions/lint-and-tests/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Lint & Test' 2 | description: 'Run linters and tests on app or library' 3 | inputs: 4 | path: 5 | required: true 6 | description: 'Path to the directory to lint and test' 7 | runs: 8 | using: 'composite' 9 | steps: 10 | - name: Use Node.js 11 | uses: actions/setup-node@v2 12 | with: 13 | node-version: ${{ env.NODE_VERSION }} 14 | - name: Use cached project dependencies 15 | uses: ./.github/actions/cache-project-dependencies 16 | - name: Run lint 17 | run: | 18 | cd ${{ inputs.path }} 19 | yarn test-linter --max-warnings 0 20 | shell: bash 21 | - name: Run stylelint 22 | run: | 23 | cd ${{ inputs.path }} 24 | if [[ $(cat package.json | jq '.scripts["test-stylelint"]') != null ]]; then 25 | yarn test-stylelint 26 | else 27 | echo "No stylelint test script found in package.json" 28 | fi 29 | shell: bash 30 | - name: Run unit tests 31 | run: | 32 | cd ${{ inputs.path }} 33 | if [[ $(cat package.json | jq '.scripts["test-unit"]') != null ]]; then 34 | yarn test-unit 35 | else 36 | echo "No unit test script found in package.json" 37 | fi 38 | shell: bash 39 | - name: Run type test 40 | run: | 41 | cd ${{ inputs.path }} 42 | if [[ $(cat package.json | jq '.scripts["test-type"]') != null ]]; then 43 | yarn test-type 44 | else 45 | echo "No type test script found in package.json" 46 | fi 47 | shell: bash 48 | - name: Run circular dependencies test 49 | run: | 50 | cd ${{ inputs.path }} 51 | if [[ $(cat package.json | jq '.scripts["test-circular"]') != null ]]; then 52 | yarn test-circular 53 | else 54 | echo "No circular dependencies test script found in package.json" 55 | fi 56 | shell: bash 57 | -------------------------------------------------------------------------------- /.github/actions/package/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Run package' 2 | description: 'Package all libraries in the monorepo' 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - name: Cache package builds 7 | id: package-cache 8 | uses: ./.github/actions/cache-package-builds 9 | - name: Run package 10 | run: if [ '${{ steps.package-cache.outputs.cache-hit }}' != 'true' ]; then yarn package; fi 11 | shell: bash 12 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | types: [opened, synchronize, reopened] 4 | 5 | # cancel previous runs on the same PR 6 | concurrency: 7 | group: ${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | name: ⛷ PR tests 11 | 12 | env: 13 | CI: true 14 | NODE_VERSION: 16 15 | 16 | defaults: 17 | run: 18 | shell: bash 19 | 20 | jobs: 21 | build: 22 | name: 🏗 Build Project 23 | runs-on: ubuntu-latest 24 | timeout-minutes: 30 25 | outputs: 26 | affected_apps: ${{ steps.get-project-changes.outputs.affected_apps }} 27 | affected_libs: ${{ steps.get-project-changes.outputs.affected_libs }} 28 | steps: 29 | - uses: actions/checkout@v2 30 | with: 31 | ref: ${{ github.event.pull_request.head.sha }} 32 | # We need to fetch all branches and commits so that Nx affected has a base to compare against. 33 | fetch-depth: 0 34 | - name: Install & cache node dependencies 35 | uses: ./.github/actions/install-node-deps 36 | - name: Package and cache builds 37 | uses: ./.github/actions/package 38 | - name: Get project changes 39 | id: get-project-changes 40 | uses: ./.github/actions/get-project-changes 41 | 42 | app-lint-and-tests: 43 | name: 🏛 App Tests 44 | needs: build 45 | runs-on: ubuntu-latest 46 | if: join(fromJson(needs.build.outputs.affected_apps)) != '' 47 | timeout-minutes: 30 48 | strategy: 49 | fail-fast: false 50 | matrix: 51 | AFFECTED_APP: ${{ fromJson(needs.build.outputs.affected_apps) }} 52 | steps: 53 | - uses: actions/checkout@v2 54 | with: 55 | ref: ${{ github.event.pull_request.head.sha }} 56 | - name: Run tests 57 | uses: ./.github/actions/lint-and-tests 58 | with: 59 | path: ${{ matrix.AFFECTED_APP }} 60 | 61 | library-lint-and-tests: 62 | name: 📚 Library Tests 63 | needs: build 64 | runs-on: ubuntu-latest 65 | if: join(fromJson(needs.build.outputs.affected_libs)) != '' 66 | timeout-minutes: 30 67 | strategy: 68 | fail-fast: false 69 | matrix: 70 | AFFECTED_LIB: ${{ fromJson(needs.build.outputs.affected_libs) }} 71 | steps: 72 | - uses: actions/checkout@v2 73 | with: 74 | ref: ${{ github.event.pull_request.head.sha }} 75 | - name: Run tests 76 | uses: ./.github/actions/lint-and-tests 77 | with: 78 | path: ${{ matrix.AFFECTED_LIB }} 79 | 80 | validate-pr: 81 | name: ✅ Validate the PR 82 | runs-on: ubuntu-latest 83 | if: ${{ always() }} 84 | needs: [build, library-lint-and-tests, app-lint-and-tests] 85 | steps: 86 | - name: Validate build 87 | run: | 88 | if [[ ${{ needs.build.result }} = "failure" ]]; then 89 | echo "build failed" 90 | exit 1 91 | fi 92 | - name: Validate app tests 93 | run: | 94 | if [[ ${{ needs.app-lint-and-tests.result }} = "failure" ]]; then 95 | echo "App tests failed" 96 | exit 1 97 | fi 98 | - name: Validate library tests 99 | run: | 100 | if [[ ${{ needs.library-lint-and-tests.result }} = "failure" ]]; then 101 | echo "Library tests failed" 102 | exit 1 103 | fi 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | .npm 4 | 5 | # Serverless directories 6 | .serverless 7 | 8 | # Webpack directories 9 | .webpack 10 | 11 | # Esbuild directories 12 | .esbuild 13 | 14 | # Ignore stack.json files 15 | **/stack.json 16 | 17 | # production 18 | /build 19 | **/dist 20 | 21 | # testing 22 | coverage 23 | 24 | # Ignore Jetbrains folder settings 25 | .idea 26 | 27 | # local env 28 | .env 29 | .env.dev 30 | .env.development 31 | .env.local 32 | 33 | # misc 34 | .DS_Store 35 | npm-debug.log* 36 | yarn-debug.log* 37 | yarn-error.log* 38 | 39 | # If people use virtualenvs for python scripts 40 | .venv 41 | 42 | # remove files to invoke lambdas locally 43 | event.json 44 | 45 | # Nx caching 46 | nx-cache 47 | 48 | # yarn 49 | .pnp.* 50 | .yarn/* 51 | !.yarn/patches 52 | !.yarn/plugins 53 | !.yarn/releases 54 | !.yarn/sdks 55 | !.yarn/versions 56 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | # "exec < /dev/tty" is to prevent husky from exiting immediately, allowing terminal to be interactive 😀 5 | # npx is used here, to allow fresko to be ran even if not installed yet 🤯 6 | exec < /dev/tty && npx fresko || true 7 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,ts}': 'yarn lint-fix', 3 | }; 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.15.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.hbs 2 | .next 3 | yarn.lock 4 | **/.serverless 5 | **/stack.json 6 | .gitlab-ci.yml 7 | .npm 8 | .webpack 9 | .esbuild 10 | **/coverage 11 | **/dist 12 | **/build 13 | **/public 14 | **/nx-cache 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "arrowParens": "avoid" 5 | } 6 | -------------------------------------------------------------------------------- /.syncpackrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dev: true, 3 | filter: '.', 4 | indent: ' ', 5 | peer: true, 6 | prod: true, 7 | semverRange: '', 8 | sortAz: [ 9 | 'contributors', 10 | 'scripts', 11 | 'dependencies', 12 | 'devDependencies', 13 | 'keywords', 14 | 'peerDependencies', 15 | ], 16 | sortFirst: [ 17 | 'name', 18 | 'description', 19 | 'private', 20 | 'version', 21 | 'author', 22 | 'license', 23 | 'homepage', 24 | 'bugs', 25 | 'repository', 26 | 'workspaces', 27 | 'sideEffects', 28 | 'files', 29 | 'main', 30 | 'module', 31 | 'types', 32 | 'contracts', 33 | 'scripts', 34 | ], 35 | versionGroups: [], 36 | }; 37 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-workspace-tools", 5 | factory: function (require) { 6 | var plugin=(()=>{var wr=Object.create,ge=Object.defineProperty,Sr=Object.defineProperties,vr=Object.getOwnPropertyDescriptor,Hr=Object.getOwnPropertyDescriptors,$r=Object.getOwnPropertyNames,Je=Object.getOwnPropertySymbols,kr=Object.getPrototypeOf,et=Object.prototype.hasOwnProperty,Tr=Object.prototype.propertyIsEnumerable;var tt=(e,t,r)=>t in e?ge(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,I=(e,t)=>{for(var r in t||(t={}))et.call(t,r)&&tt(e,r,t[r]);if(Je)for(var r of Je(t))Tr.call(t,r)&&tt(e,r,t[r]);return e},F=(e,t)=>Sr(e,Hr(t)),Lr=e=>ge(e,"__esModule",{value:!0});var K=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Or=(e,t)=>{for(var r in t)ge(e,r,{get:t[r],enumerable:!0})},Nr=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of $r(t))!et.call(e,n)&&n!=="default"&&ge(e,n,{get:()=>t[n],enumerable:!(r=vr(t,n))||r.enumerable});return e},Q=e=>Nr(Lr(ge(e!=null?wr(kr(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var He=K(ee=>{"use strict";ee.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;ee.find=(e,t)=>e.nodes.find(r=>r.type===t);ee.exceedsLimit=(e,t,r=1,n)=>n===!1||!ee.isInteger(e)||!ee.isInteger(t)?!1:(Number(t)-Number(e))/Number(r)>=n;ee.escapeNode=(e,t=0,r)=>{let n=e.nodes[t];!n||(r&&n.type===r||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};ee.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0==0?(e.invalid=!0,!0):!1;ee.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0==0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;ee.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;ee.reduce=e=>e.reduce((t,r)=>(r.type==="text"&&t.push(r.value),r.type==="range"&&(r.type="text"),t),[]);ee.flatten=(...e)=>{let t=[],r=n=>{for(let s=0;s{"use strict";var at=He();st.exports=(e,t={})=>{let r=(n,s={})=>{let a=t.escapeInvalid&&at.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o="";if(n.value)return(a||i)&&at.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let h of n.nodes)o+=r(h);return o};return r(e)}});var ot=K((os,it)=>{"use strict";it.exports=function(e){return typeof e=="number"?e-e==0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var mt=K((us,ut)=>{"use strict";var ct=ot(),pe=(e,t,r)=>{if(ct(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(t===void 0||e===t)return String(e);if(ct(t)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n=I({relaxZeros:!0},r);typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),a=String(n.shorthand),i=String(n.capture),o=String(n.wrap),h=e+":"+t+"="+s+a+i+o;if(pe.cache.hasOwnProperty(h))return pe.cache[h].result;let m=Math.min(e,t),f=Math.max(e,t);if(Math.abs(m-f)===1){let y=e+"|"+t;return n.capture?`(${y})`:n.wrap===!1?y:`(?:${y})`}let R=pt(e)||pt(t),p={min:e,max:t,a:m,b:f},v=[],_=[];if(R&&(p.isPadded=R,p.maxLen=String(p.max).length),m<0){let y=f<0?Math.abs(f):1;_=lt(y,Math.abs(m),p,n),m=p.a=0}return f>=0&&(v=lt(m,f,p,n)),p.negatives=_,p.positives=v,p.result=Ir(_,v,n),n.capture===!0?p.result=`(${p.result})`:n.wrap!==!1&&v.length+_.length>1&&(p.result=`(?:${p.result})`),pe.cache[h]=p,p.result};function Ir(e,t,r){let n=Pe(e,t,"-",!1,r)||[],s=Pe(t,e,"",!1,r)||[],a=Pe(e,t,"-?",!0,r)||[];return n.concat(a).concat(s).join("|")}function Mr(e,t){let r=1,n=1,s=ft(e,r),a=new Set([t]);for(;e<=s&&s<=t;)a.add(s),r+=1,s=ft(e,r);for(s=ht(t+1,n)-1;e1&&o.count.pop(),o.count.push(f.count[0]),o.string=o.pattern+dt(o.count),i=m+1;continue}r.isPadded&&(R=Ur(m,r,n)),f.string=R+f.pattern+dt(f.count),a.push(f),i=m+1,o=f}return a}function Pe(e,t,r,n,s){let a=[];for(let i of e){let{string:o}=i;!n&&!gt(t,"string",o)&&a.push(r+o),n&>(t,"string",o)&&a.push(r+o)}return a}function Pr(e,t){let r=[];for(let n=0;nt?1:t>e?-1:0}function gt(e,t,r){return e.some(n=>n[t]===r)}function ft(e,t){return Number(String(e).slice(0,-t)+"9".repeat(t))}function ht(e,t){return e-e%Math.pow(10,t)}function dt(e){let[t=0,r=""]=e;return r||t>1?`{${t+(r?","+r:"")}}`:""}function Dr(e,t,r){return`[${e}${t-e==1?"":"-"}${t}]`}function pt(e){return/^-?(0+)\d/.test(e)}function Ur(e,t,r){if(!t.isPadded)return e;let n=Math.abs(t.maxLen-String(e).length),s=r.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}pe.cache={};pe.clearCache=()=>pe.cache={};ut.exports=pe});var Ue=K((cs,At)=>{"use strict";var qr=require("util"),Rt=mt(),yt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Kr=e=>t=>e===!0?Number(t):String(t),De=e=>typeof e=="number"||typeof e=="string"&&e!=="",Ae=e=>Number.isInteger(+e),Ge=e=>{let t=`${e}`,r=-1;if(t[0]==="-"&&(t=t.slice(1)),t==="0")return!1;for(;t[++r]==="0";);return r>0},Wr=(e,t,r)=>typeof e=="string"||typeof t=="string"?!0:r.stringify===!0,jr=(e,t,r)=>{if(t>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?t-1:t,"0")}return r===!1?String(e):e},_t=(e,t)=>{let r=e[0]==="-"?"-":"";for(r&&(e=e.slice(1),t--);e.length{e.negatives.sort((i,o)=>io?1:0),e.positives.sort((i,o)=>io?1:0);let r=t.capture?"":"?:",n="",s="",a;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${r}${e.negatives.join("|")})`),n&&s?a=`${n}|${s}`:a=n||s,t.wrap?`(${r}${a})`:a},bt=(e,t,r,n)=>{if(r)return Rt(e,t,I({wrap:!1},n));let s=String.fromCharCode(e);if(e===t)return s;let a=String.fromCharCode(t);return`[${s}-${a}]`},Et=(e,t,r)=>{if(Array.isArray(e)){let n=r.wrap===!0,s=r.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return Rt(e,t,r)},xt=(...e)=>new RangeError("Invalid range arguments: "+qr.inspect(...e)),Ct=(e,t,r)=>{if(r.strictRanges===!0)throw xt([e,t]);return[]},Qr=(e,t)=>{if(t.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Xr=(e,t,r=1,n={})=>{let s=Number(e),a=Number(t);if(!Number.isInteger(s)||!Number.isInteger(a)){if(n.strictRanges===!0)throw xt([e,t]);return[]}s===0&&(s=0),a===0&&(a=0);let i=s>a,o=String(e),h=String(t),m=String(r);r=Math.max(Math.abs(r),1);let f=Ge(o)||Ge(h)||Ge(m),R=f?Math.max(o.length,h.length,m.length):0,p=f===!1&&Wr(e,t,n)===!1,v=n.transform||Kr(p);if(n.toRegex&&r===1)return bt(_t(e,R),_t(t,R),!0,n);let _={negatives:[],positives:[]},y=H=>_[H<0?"negatives":"positives"].push(Math.abs(H)),b=[],E=0;for(;i?s>=a:s<=a;)n.toRegex===!0&&r>1?y(s):b.push(jr(v(s,E),R,p)),s=i?s-r:s+r,E++;return n.toRegex===!0?r>1?Fr(_,n):Et(b,null,I({wrap:!1},n)):b},Zr=(e,t,r=1,n={})=>{if(!Ae(e)&&e.length>1||!Ae(t)&&t.length>1)return Ct(e,t,n);let s=n.transform||(p=>String.fromCharCode(p)),a=`${e}`.charCodeAt(0),i=`${t}`.charCodeAt(0),o=a>i,h=Math.min(a,i),m=Math.max(a,i);if(n.toRegex&&r===1)return bt(h,m,!1,n);let f=[],R=0;for(;o?a>=i:a<=i;)f.push(s(a,R)),a=o?a-r:a+r,R++;return n.toRegex===!0?Et(f,null,{wrap:!1,options:n}):f},ke=(e,t,r,n={})=>{if(t==null&&De(e))return[e];if(!De(e)||!De(t))return Ct(e,t,n);if(typeof r=="function")return ke(e,t,1,{transform:r});if(yt(r))return ke(e,t,0,r);let s=I({},n);return s.capture===!0&&(s.wrap=!0),r=r||s.step||1,Ae(r)?Ae(e)&&Ae(t)?Xr(e,t,r,s):Zr(e,t,Math.max(Math.abs(r),1),s):r!=null&&!yt(r)?Qr(r,s):ke(e,t,1,r)};At.exports=ke});var vt=K((ls,wt)=>{"use strict";var Yr=Ue(),St=He(),zr=(e,t={})=>{let r=(n,s={})=>{let a=St.isInvalidBrace(s),i=n.invalid===!0&&t.escapeInvalid===!0,o=a===!0||i===!0,h=t.escapeInvalid===!0?"\\":"",m="";if(n.isOpen===!0||n.isClose===!0)return h+n.value;if(n.type==="open")return o?h+n.value:"(";if(n.type==="close")return o?h+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":o?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let f=St.reduce(n.nodes),R=Yr(...f,F(I({},t),{wrap:!1,toRegex:!0}));if(R.length!==0)return f.length>1&&R.length>1?`(${R})`:R}if(n.nodes)for(let f of n.nodes)m+=r(f,n);return m};return r(e)};wt.exports=zr});var kt=K((ps,Ht)=>{"use strict";var Vr=Ue(),$t=$e(),he=He(),fe=(e="",t="",r=!1)=>{let n=[];if(e=[].concat(e),t=[].concat(t),!t.length)return e;if(!e.length)return r?he.flatten(t).map(s=>`{${s}}`):t;for(let s of e)if(Array.isArray(s))for(let a of s)n.push(fe(a,t,r));else for(let a of t)r===!0&&typeof a=="string"&&(a=`{${a}}`),n.push(Array.isArray(a)?fe(s,a,r):s+a);return he.flatten(n)},Jr=(e,t={})=>{let r=t.rangeLimit===void 0?1e3:t.rangeLimit,n=(s,a={})=>{s.queue=[];let i=a,o=a.queue;for(;i.type!=="brace"&&i.type!=="root"&&i.parent;)i=i.parent,o=i.queue;if(s.invalid||s.dollar){o.push(fe(o.pop(),$t(s,t)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){o.push(fe(o.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let R=he.reduce(s.nodes);if(he.exceedsLimit(...R,t.step,r))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let p=Vr(...R,t);p.length===0&&(p=$t(s,t)),o.push(fe(o.pop(),p)),s.nodes=[];return}let h=he.encloseBrace(s),m=s.queue,f=s;for(;f.type!=="brace"&&f.type!=="root"&&f.parent;)f=f.parent,m=f.queue;for(let R=0;R{"use strict";Tt.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` 7 | `,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Mt=K((hs,Ot)=>{"use strict";var en=$e(),{MAX_LENGTH:Nt,CHAR_BACKSLASH:qe,CHAR_BACKTICK:tn,CHAR_COMMA:rn,CHAR_DOT:nn,CHAR_LEFT_PARENTHESES:sn,CHAR_RIGHT_PARENTHESES:an,CHAR_LEFT_CURLY_BRACE:on,CHAR_RIGHT_CURLY_BRACE:un,CHAR_LEFT_SQUARE_BRACKET:It,CHAR_RIGHT_SQUARE_BRACKET:Bt,CHAR_DOUBLE_QUOTE:cn,CHAR_SINGLE_QUOTE:ln,CHAR_NO_BREAK_SPACE:pn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:fn}=Lt(),hn=(e,t={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let r=t||{},n=typeof r.maxLength=="number"?Math.min(Nt,r.maxLength):Nt;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},a=[s],i=s,o=s,h=0,m=e.length,f=0,R=0,p,v={},_=()=>e[f++],y=b=>{if(b.type==="text"&&o.type==="dot"&&(o.type="text"),o&&o.type==="text"&&b.type==="text"){o.value+=b.value;return}return i.nodes.push(b),b.parent=i,b.prev=o,o=b,b};for(y({type:"bos"});f0){if(i.ranges>0){i.ranges=0;let b=i.nodes.shift();i.nodes=[b,{type:"text",value:en(i)}]}y({type:"comma",value:p}),i.commas++;continue}if(p===nn&&R>0&&i.commas===0){let b=i.nodes;if(R===0||b.length===0){y({type:"text",value:p});continue}if(o.type==="dot"){if(i.range=[],o.value+=p,o.type="range",i.nodes.length!==3&&i.nodes.length!==5){i.invalid=!0,i.ranges=0,o.type="text";continue}i.ranges++,i.args=[];continue}if(o.type==="range"){b.pop();let E=b[b.length-1];E.value+=o.value+p,o=E,i.ranges--;continue}y({type:"dot",value:p});continue}y({type:"text",value:p})}do if(i=a.pop(),i.type!=="root"){i.nodes.forEach(H=>{H.nodes||(H.type==="open"&&(H.isOpen=!0),H.type==="close"&&(H.isClose=!0),H.nodes||(H.type="text"),H.invalid=!0)});let b=a[a.length-1],E=b.nodes.indexOf(i);b.nodes.splice(E,1,...i.nodes)}while(a.length>0);return y({type:"eos"}),s};Ot.exports=hn});var Gt=K((ds,Pt)=>{"use strict";var Dt=$e(),dn=vt(),gn=kt(),mn=Mt(),z=(e,t={})=>{let r=[];if(Array.isArray(e))for(let n of e){let s=z.create(n,t);Array.isArray(s)?r.push(...s):r.push(s)}else r=[].concat(z.create(e,t));return t&&t.expand===!0&&t.nodupes===!0&&(r=[...new Set(r)]),r};z.parse=(e,t={})=>mn(e,t);z.stringify=(e,t={})=>typeof e=="string"?Dt(z.parse(e,t),t):Dt(e,t);z.compile=(e,t={})=>(typeof e=="string"&&(e=z.parse(e,t)),dn(e,t));z.expand=(e,t={})=>{typeof e=="string"&&(e=z.parse(e,t));let r=gn(e,t);return t.noempty===!0&&(r=r.filter(Boolean)),t.nodupes===!0&&(r=[...new Set(r)]),r};z.create=(e,t={})=>e===""||e.length<3?[e]:t.expand!==!0?z.compile(e,t):z.expand(e,t);Pt.exports=z});var Re=K((gs,Ut)=>{"use strict";var An=require("path"),se="\\\\/",qt=`[^${se}]`,ue="\\.",Rn="\\+",yn="\\?",Te="\\/",_n="(?=.)",Kt="[^/]",Ke=`(?:${Te}|$)`,Wt=`(?:^|${Te})`,We=`${ue}{1,2}${Ke}`,bn=`(?!${ue})`,En=`(?!${Wt}${We})`,xn=`(?!${ue}{0,1}${Ke})`,Cn=`(?!${We})`,wn=`[^.${Te}]`,Sn=`${Kt}*?`,jt={DOT_LITERAL:ue,PLUS_LITERAL:Rn,QMARK_LITERAL:yn,SLASH_LITERAL:Te,ONE_CHAR:_n,QMARK:Kt,END_ANCHOR:Ke,DOTS_SLASH:We,NO_DOT:bn,NO_DOTS:En,NO_DOT_SLASH:xn,NO_DOTS_SLASH:Cn,QMARK_NO_DOT:wn,STAR:Sn,START_ANCHOR:Wt},vn=F(I({},jt),{SLASH_LITERAL:`[${se}]`,QMARK:qt,STAR:`${qt}*?`,DOTS_SLASH:`${ue}{1,2}(?:[${se}]|$)`,NO_DOT:`(?!${ue})`,NO_DOTS:`(?!(?:^|[${se}])${ue}{1,2}(?:[${se}]|$))`,NO_DOT_SLASH:`(?!${ue}{0,1}(?:[${se}]|$))`,NO_DOTS_SLASH:`(?!${ue}{1,2}(?:[${se}]|$))`,QMARK_NO_DOT:`[^.${se}]`,START_ANCHOR:`(?:^|[${se}])`,END_ANCHOR:`(?:[${se}]|$)`}),Hn={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};Ut.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:Hn,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:An.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?vn:jt}}});var ye=K(X=>{"use strict";var $n=require("path"),kn=process.platform==="win32",{REGEX_BACKSLASH:Tn,REGEX_REMOVE_BACKSLASH:Ln,REGEX_SPECIAL_CHARS:On,REGEX_SPECIAL_CHARS_GLOBAL:Nn}=Re();X.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);X.hasRegexChars=e=>On.test(e);X.isRegexChar=e=>e.length===1&&X.hasRegexChars(e);X.escapeRegex=e=>e.replace(Nn,"\\$1");X.toPosixSlashes=e=>e.replace(Tn,"/");X.removeBackslashes=e=>e.replace(Ln,t=>t==="\\"?"":t);X.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};X.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:kn===!0||$n.sep==="\\";X.escapeLast=(e,t,r)=>{let n=e.lastIndexOf(t,r);return n===-1?e:e[n-1]==="\\"?X.escapeLast(e,t,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};X.removePrefix=(e,t={})=>{let r=e;return r.startsWith("./")&&(r=r.slice(2),t.prefix="./"),r};X.wrapOutput=(e,t={},r={})=>{let n=r.contains?"":"^",s=r.contains?"":"$",a=`${n}(?:${e})${s}`;return t.negated===!0&&(a=`(?:^(?!${a}).*$)`),a}});var er=K((As,Ft)=>{"use strict";var Qt=ye(),{CHAR_ASTERISK:je,CHAR_AT:In,CHAR_BACKWARD_SLASH:_e,CHAR_COMMA:Bn,CHAR_DOT:Fe,CHAR_EXCLAMATION_MARK:Xt,CHAR_FORWARD_SLASH:Zt,CHAR_LEFT_CURLY_BRACE:Qe,CHAR_LEFT_PARENTHESES:Xe,CHAR_LEFT_SQUARE_BRACKET:Mn,CHAR_PLUS:Pn,CHAR_QUESTION_MARK:Yt,CHAR_RIGHT_CURLY_BRACE:Dn,CHAR_RIGHT_PARENTHESES:zt,CHAR_RIGHT_SQUARE_BRACKET:Gn}=Re(),Vt=e=>e===Zt||e===_e,Jt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?Infinity:1)},Un=(e,t)=>{let r=t||{},n=e.length-1,s=r.parts===!0||r.scanToEnd===!0,a=[],i=[],o=[],h=e,m=-1,f=0,R=0,p=!1,v=!1,_=!1,y=!1,b=!1,E=!1,H=!1,L=!1,k=!1,J=0,ie,g,w={value:"",depth:0,isGlob:!1},D=()=>m>=n,W=()=>h.charCodeAt(m+1),l=()=>(ie=g,h.charCodeAt(++m));for(;m0&&(T=h.slice(0,f),h=h.slice(f),R-=f),x&&_===!0&&R>0?(x=h.slice(0,R),U=h.slice(R)):_===!0?(x="",U=h):x=h,x&&x!==""&&x!=="/"&&x!==h&&Vt(x.charCodeAt(x.length-1))&&(x=x.slice(0,-1)),r.unescape===!0&&(U&&(U=Qt.removeBackslashes(U)),x&&H===!0&&(x=Qt.removeBackslashes(x)));let u={prefix:T,input:e,start:f,base:x,glob:U,isBrace:p,isBracket:v,isGlob:_,isExtglob:y,isGlobstar:b,negated:L};if(r.tokens===!0&&(u.maxDepth=0,Vt(g)||i.push(w),u.tokens=i),r.parts===!0||r.tokens===!0){let c;for(let $=0;${"use strict";var Le=Re(),V=ye(),{MAX_LENGTH:Oe,POSIX_REGEX_SOURCE:qn,REGEX_NON_SPECIAL_CHARS:Kn,REGEX_SPECIAL_CHARS_BACKREF:Wn,REPLACEMENTS:rr}=Le,jn=(e,t)=>{if(typeof t.expandRange=="function")return t.expandRange(...e,t);e.sort();let r=`[${e.join("-")}]`;try{new RegExp(r)}catch(n){return e.map(s=>V.escapeRegex(s)).join("..")}return r},de=(e,t)=>`Missing ${e}: "${t}" - use "\\\\${t}" to match literal characters`,nr=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=rr[e]||e;let r=I({},t),n=typeof r.maxLength=="number"?Math.min(Oe,r.maxLength):Oe,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let a={type:"bos",value:"",output:r.prepend||""},i=[a],o=r.capture?"":"?:",h=V.isWindows(t),m=Le.globChars(h),f=Le.extglobChars(m),{DOT_LITERAL:R,PLUS_LITERAL:p,SLASH_LITERAL:v,ONE_CHAR:_,DOTS_SLASH:y,NO_DOT:b,NO_DOT_SLASH:E,NO_DOTS_SLASH:H,QMARK:L,QMARK_NO_DOT:k,STAR:J,START_ANCHOR:ie}=m,g=A=>`(${o}(?:(?!${ie}${A.dot?y:R}).)*?)`,w=r.dot?"":b,D=r.dot?L:k,W=r.bash===!0?g(r):J;r.capture&&(W=`(${W})`),typeof r.noext=="boolean"&&(r.noextglob=r.noext);let l={input:e,index:-1,start:0,dot:r.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:i};e=V.removePrefix(e,l),s=e.length;let x=[],T=[],U=[],u=a,c,$=()=>l.index===s-1,B=l.peek=(A=1)=>e[l.index+A],Y=l.advance=()=>e[++l.index],re=()=>e.slice(l.index+1),oe=(A="",O=0)=>{l.consumed+=A,l.index+=O},xe=A=>{l.output+=A.output!=null?A.output:A.value,oe(A.value)},xr=()=>{let A=1;for(;B()==="!"&&(B(2)!=="("||B(3)==="?");)Y(),l.start++,A++;return A%2==0?!1:(l.negated=!0,l.start++,!0)},Ce=A=>{l[A]++,U.push(A)},ce=A=>{l[A]--,U.pop()},C=A=>{if(u.type==="globstar"){let O=l.braces>0&&(A.type==="comma"||A.type==="brace"),d=A.extglob===!0||x.length&&(A.type==="pipe"||A.type==="paren");A.type!=="slash"&&A.type!=="paren"&&!O&&!d&&(l.output=l.output.slice(0,-u.output.length),u.type="star",u.value="*",u.output=W,l.output+=u.output)}if(x.length&&A.type!=="paren"&&!f[A.value]&&(x[x.length-1].inner+=A.value),(A.value||A.output)&&xe(A),u&&u.type==="text"&&A.type==="text"){u.value+=A.value,u.output=(u.output||"")+A.value;return}A.prev=u,i.push(A),u=A},we=(A,O)=>{let d=F(I({},f[O]),{conditions:1,inner:""});d.prev=u,d.parens=l.parens,d.output=l.output;let S=(r.capture?"(":"")+d.open;Ce("parens"),C({type:A,value:O,output:l.output?"":_}),C({type:"paren",extglob:!0,value:Y(),output:S}),x.push(d)},Cr=A=>{let O=A.close+(r.capture?")":"");if(A.type==="negate"){let d=W;A.inner&&A.inner.length>1&&A.inner.includes("/")&&(d=g(r)),(d!==W||$()||/^\)+$/.test(re()))&&(O=A.close=`)$))${d}`),A.prev.type==="bos"&&(l.negatedExtglob=!0)}C({type:"paren",extglob:!0,value:c,output:O}),ce("parens")};if(r.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let A=!1,O=e.replace(Wn,(d,S,M,j,q,Me)=>j==="\\"?(A=!0,d):j==="?"?S?S+j+(q?L.repeat(q.length):""):Me===0?D+(q?L.repeat(q.length):""):L.repeat(M.length):j==="."?R.repeat(M.length):j==="*"?S?S+j+(q?W:""):W:S?d:`\\${d}`);return A===!0&&(r.unescape===!0?O=O.replace(/\\/g,""):O=O.replace(/\\+/g,d=>d.length%2==0?"\\\\":d?"\\":"")),O===e&&r.contains===!0?(l.output=e,l):(l.output=V.wrapOutput(O,l,t),l)}for(;!$();){if(c=Y(),c==="\0")continue;if(c==="\\"){let d=B();if(d==="/"&&r.bash!==!0||d==="."||d===";")continue;if(!d){c+="\\",C({type:"text",value:c});continue}let S=/^\\+/.exec(re()),M=0;if(S&&S[0].length>2&&(M=S[0].length,l.index+=M,M%2!=0&&(c+="\\")),r.unescape===!0?c=Y()||"":c+=Y()||"",l.brackets===0){C({type:"text",value:c});continue}}if(l.brackets>0&&(c!=="]"||u.value==="["||u.value==="[^")){if(r.posix!==!1&&c===":"){let d=u.value.slice(1);if(d.includes("[")&&(u.posix=!0,d.includes(":"))){let S=u.value.lastIndexOf("["),M=u.value.slice(0,S),j=u.value.slice(S+2),q=qn[j];if(q){u.value=M+q,l.backtrack=!0,Y(),!a.output&&i.indexOf(u)===1&&(a.output=_);continue}}}(c==="["&&B()!==":"||c==="-"&&B()==="]")&&(c=`\\${c}`),c==="]"&&(u.value==="["||u.value==="[^")&&(c=`\\${c}`),r.posix===!0&&c==="!"&&u.value==="["&&(c="^"),u.value+=c,xe({value:c});continue}if(l.quotes===1&&c!=='"'){c=V.escapeRegex(c),u.value+=c,xe({value:c});continue}if(c==='"'){l.quotes=l.quotes===1?0:1,r.keepQuotes===!0&&C({type:"text",value:c});continue}if(c==="("){Ce("parens"),C({type:"paren",value:c});continue}if(c===")"){if(l.parens===0&&r.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=x[x.length-1];if(d&&l.parens===d.parens+1){Cr(x.pop());continue}C({type:"paren",value:c,output:l.parens?")":"\\)"}),ce("parens");continue}if(c==="["){if(r.nobracket===!0||!re().includes("]")){if(r.nobracket!==!0&&r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));c=`\\${c}`}else Ce("brackets");C({type:"bracket",value:c});continue}if(c==="]"){if(r.nobracket===!0||u&&u.type==="bracket"&&u.value.length===1){C({type:"text",value:c,output:`\\${c}`});continue}if(l.brackets===0){if(r.strictBrackets===!0)throw new SyntaxError(de("opening","["));C({type:"text",value:c,output:`\\${c}`});continue}ce("brackets");let d=u.value.slice(1);if(u.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(c=`/${c}`),u.value+=c,xe({value:c}),r.literalBrackets===!1||V.hasRegexChars(d))continue;let S=V.escapeRegex(u.value);if(l.output=l.output.slice(0,-u.value.length),r.literalBrackets===!0){l.output+=S,u.value=S;continue}u.value=`(${o}${S}|${u.value})`,l.output+=u.value;continue}if(c==="{"&&r.nobrace!==!0){Ce("braces");let d={type:"brace",value:c,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};T.push(d),C(d);continue}if(c==="}"){let d=T[T.length-1];if(r.nobrace===!0||!d){C({type:"text",value:c,output:c});continue}let S=")";if(d.dots===!0){let M=i.slice(),j=[];for(let q=M.length-1;q>=0&&(i.pop(),M[q].type!=="brace");q--)M[q].type!=="dots"&&j.unshift(M[q].value);S=jn(j,r),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let M=l.output.slice(0,d.outputIndex),j=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",c=S="\\}",l.output=M;for(let q of j)l.output+=q.output||q.value}C({type:"brace",value:c,output:S}),ce("braces"),T.pop();continue}if(c==="|"){x.length>0&&x[x.length-1].conditions++,C({type:"text",value:c});continue}if(c===","){let d=c,S=T[T.length-1];S&&U[U.length-1]==="braces"&&(S.comma=!0,d="|"),C({type:"comma",value:c,output:d});continue}if(c==="/"){if(u.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",i.pop(),u=a;continue}C({type:"slash",value:c,output:v});continue}if(c==="."){if(l.braces>0&&u.type==="dot"){u.value==="."&&(u.output=R);let d=T[T.length-1];u.type="dots",u.output+=c,u.value+=c,d.dots=!0;continue}if(l.braces+l.parens===0&&u.type!=="bos"&&u.type!=="slash"){C({type:"text",value:c,output:R});continue}C({type:"dot",value:c,output:R});continue}if(c==="?"){if(!(u&&u.value==="(")&&r.noextglob!==!0&&B()==="("&&B(2)!=="?"){we("qmark",c);continue}if(u&&u.type==="paren"){let S=B(),M=c;if(S==="<"&&!V.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(u.value==="("&&!/[!=<:]/.test(S)||S==="<"&&!/<([!=]|\w+>)/.test(re()))&&(M=`\\${c}`),C({type:"text",value:c,output:M});continue}if(r.dot!==!0&&(u.type==="slash"||u.type==="bos")){C({type:"qmark",value:c,output:k});continue}C({type:"qmark",value:c,output:L});continue}if(c==="!"){if(r.noextglob!==!0&&B()==="("&&(B(2)!=="?"||!/[!=<:]/.test(B(3)))){we("negate",c);continue}if(r.nonegate!==!0&&l.index===0){xr();continue}}if(c==="+"){if(r.noextglob!==!0&&B()==="("&&B(2)!=="?"){we("plus",c);continue}if(u&&u.value==="("||r.regex===!1){C({type:"plus",value:c,output:p});continue}if(u&&(u.type==="bracket"||u.type==="paren"||u.type==="brace")||l.parens>0){C({type:"plus",value:c});continue}C({type:"plus",value:p});continue}if(c==="@"){if(r.noextglob!==!0&&B()==="("&&B(2)!=="?"){C({type:"at",extglob:!0,value:c,output:""});continue}C({type:"text",value:c});continue}if(c!=="*"){(c==="$"||c==="^")&&(c=`\\${c}`);let d=Kn.exec(re());d&&(c+=d[0],l.index+=d[0].length),C({type:"text",value:c});continue}if(u&&(u.type==="globstar"||u.star===!0)){u.type="star",u.star=!0,u.value+=c,u.output=W,l.backtrack=!0,l.globstar=!0,oe(c);continue}let A=re();if(r.noextglob!==!0&&/^\([^?]/.test(A)){we("star",c);continue}if(u.type==="star"){if(r.noglobstar===!0){oe(c);continue}let d=u.prev,S=d.prev,M=d.type==="slash"||d.type==="bos",j=S&&(S.type==="star"||S.type==="globstar");if(r.bash===!0&&(!M||A[0]&&A[0]!=="/")){C({type:"star",value:c,output:""});continue}let q=l.braces>0&&(d.type==="comma"||d.type==="brace"),Me=x.length&&(d.type==="pipe"||d.type==="paren");if(!M&&d.type!=="paren"&&!q&&!Me){C({type:"star",value:c,output:""});continue}for(;A.slice(0,3)==="/**";){let Se=e[l.index+4];if(Se&&Se!=="/")break;A=A.slice(3),oe("/**",3)}if(d.type==="bos"&&$()){u.type="globstar",u.value+=c,u.output=g(r),l.output=u.output,l.globstar=!0,oe(c);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!j&&$()){l.output=l.output.slice(0,-(d.output+u.output).length),d.output=`(?:${d.output}`,u.type="globstar",u.output=g(r)+(r.strictSlashes?")":"|$)"),u.value+=c,l.globstar=!0,l.output+=d.output+u.output,oe(c);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&A[0]==="/"){let Se=A[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+u.output).length),d.output=`(?:${d.output}`,u.type="globstar",u.output=`${g(r)}${v}|${v}${Se})`,u.value+=c,l.output+=d.output+u.output,l.globstar=!0,oe(c+Y()),C({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&A[0]==="/"){u.type="globstar",u.value+=c,u.output=`(?:^|${v}|${g(r)}${v})`,l.output=u.output,l.globstar=!0,oe(c+Y()),C({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-u.output.length),u.type="globstar",u.output=g(r),u.value+=c,l.output+=u.output,l.globstar=!0,oe(c);continue}let O={type:"star",value:c,output:W};if(r.bash===!0){O.output=".*?",(u.type==="bos"||u.type==="slash")&&(O.output=w+O.output),C(O);continue}if(u&&(u.type==="bracket"||u.type==="paren")&&r.regex===!0){O.output=c,C(O);continue}(l.index===l.start||u.type==="slash"||u.type==="dot")&&(u.type==="dot"?(l.output+=E,u.output+=E):r.dot===!0?(l.output+=H,u.output+=H):(l.output+=w,u.output+=w),B()!=="*"&&(l.output+=_,u.output+=_)),C(O)}for(;l.brackets>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=V.escapeLast(l.output,"["),ce("brackets")}for(;l.parens>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=V.escapeLast(l.output,"("),ce("parens")}for(;l.braces>0;){if(r.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=V.escapeLast(l.output,"{"),ce("braces")}if(r.strictSlashes!==!0&&(u.type==="star"||u.type==="bracket")&&C({type:"maybe_slash",value:"",output:`${v}?`}),l.backtrack===!0){l.output="";for(let A of l.tokens)l.output+=A.output!=null?A.output:A.value,A.suffix&&(l.output+=A.suffix)}return l};nr.fastpaths=(e,t)=>{let r=I({},t),n=typeof r.maxLength=="number"?Math.min(Oe,r.maxLength):Oe,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=rr[e]||e;let a=V.isWindows(t),{DOT_LITERAL:i,SLASH_LITERAL:o,ONE_CHAR:h,DOTS_SLASH:m,NO_DOT:f,NO_DOTS:R,NO_DOTS_SLASH:p,STAR:v,START_ANCHOR:_}=Le.globChars(a),y=r.dot?R:f,b=r.dot?p:f,E=r.capture?"":"?:",H={negated:!1,prefix:""},L=r.bash===!0?".*?":v;r.capture&&(L=`(${L})`);let k=w=>w.noglobstar===!0?L:`(${E}(?:(?!${_}${w.dot?m:i}).)*?)`,J=w=>{switch(w){case"*":return`${y}${h}${L}`;case".*":return`${i}${h}${L}`;case"*.*":return`${y}${L}${i}${h}${L}`;case"*/*":return`${y}${L}${o}${h}${b}${L}`;case"**":return y+k(r);case"**/*":return`(?:${y}${k(r)}${o})?${b}${h}${L}`;case"**/*.*":return`(?:${y}${k(r)}${o})?${b}${L}${i}${h}${L}`;case"**/.*":return`(?:${y}${k(r)}${o})?${i}${h}${L}`;default:{let D=/^(.*?)\.(\w+)$/.exec(w);if(!D)return;let W=J(D[1]);return W?W+i+D[2]:void 0}}},ie=V.removePrefix(e,H),g=J(ie);return g&&r.strictSlashes!==!0&&(g+=`${o}?`),g};tr.exports=nr});var ir=K((ys,ar)=>{"use strict";var Fn=require("path"),Qn=er(),Ze=sr(),Ye=ye(),Xn=Re(),Zn=e=>e&&typeof e=="object"&&!Array.isArray(e),P=(e,t,r=!1)=>{if(Array.isArray(e)){let f=e.map(p=>P(p,t,r));return p=>{for(let v of f){let _=v(p);if(_)return _}return!1}}let n=Zn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=t||{},a=Ye.isWindows(t),i=n?P.compileRe(e,t):P.makeRe(e,t,!1,!0),o=i.state;delete i.state;let h=()=>!1;if(s.ignore){let f=F(I({},t),{ignore:null,onMatch:null,onResult:null});h=P(s.ignore,f,r)}let m=(f,R=!1)=>{let{isMatch:p,match:v,output:_}=P.test(f,i,t,{glob:e,posix:a}),y={glob:e,state:o,regex:i,posix:a,input:f,output:_,match:v,isMatch:p};return typeof s.onResult=="function"&&s.onResult(y),p===!1?(y.isMatch=!1,R?y:!1):h(f)?(typeof s.onIgnore=="function"&&s.onIgnore(y),y.isMatch=!1,R?y:!1):(typeof s.onMatch=="function"&&s.onMatch(y),R?y:!0)};return r&&(m.state=o),m};P.test=(e,t,r,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let a=r||{},i=a.format||(s?Ye.toPosixSlashes:null),o=e===n,h=o&&i?i(e):e;return o===!1&&(h=i?i(e):e,o=h===n),(o===!1||a.capture===!0)&&(a.matchBase===!0||a.basename===!0?o=P.matchBase(e,t,r,s):o=t.exec(h)),{isMatch:Boolean(o),match:o,output:h}};P.matchBase=(e,t,r,n=Ye.isWindows(r))=>(t instanceof RegExp?t:P.makeRe(t,r)).test(Fn.basename(e));P.isMatch=(e,t,r)=>P(t,r)(e);P.parse=(e,t)=>Array.isArray(e)?e.map(r=>P.parse(r,t)):Ze(e,F(I({},t),{fastpaths:!1}));P.scan=(e,t)=>Qn(e,t);P.compileRe=(e,t,r=!1,n=!1)=>{if(r===!0)return e.output;let s=t||{},a=s.contains?"":"^",i=s.contains?"":"$",o=`${a}(?:${e.output})${i}`;e&&e.negated===!0&&(o=`^(?!${o}).*$`);let h=P.toRegex(o,t);return n===!0&&(h.state=e),h};P.makeRe=(e,t,r=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s=t||{},a={negated:!1,fastpaths:!0},i="",o;return e.startsWith("./")&&(e=e.slice(2),i=a.prefix="./"),s.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(o=Ze.fastpaths(e,t)),o===void 0?(a=Ze(e,t),a.prefix=i+(a.prefix||"")):a.output=o,P.compileRe(a,t,r,n)};P.toRegex=(e,t)=>{try{let r=t||{};return new RegExp(e,r.flags||(r.nocase?"i":""))}catch(r){if(t&&t.debug===!0)throw r;return/$^/}};P.constants=Xn;ar.exports=P});var ur=K((_s,or)=>{"use strict";or.exports=ir()});var hr=K((bs,cr)=>{"use strict";var lr=require("util"),pr=Gt(),ae=ur(),ze=ye(),fr=e=>typeof e=="string"&&(e===""||e==="./"),N=(e,t,r)=>{t=[].concat(t),e=[].concat(e);let n=new Set,s=new Set,a=new Set,i=0,o=f=>{a.add(f.output),r&&r.onResult&&r.onResult(f)};for(let f=0;f!n.has(f));if(r&&m.length===0){if(r.failglob===!0)throw new Error(`No matches found for "${t.join(", ")}"`);if(r.nonull===!0||r.nullglob===!0)return r.unescape?t.map(f=>f.replace(/\\/g,"")):t}return m};N.match=N;N.matcher=(e,t)=>ae(e,t);N.isMatch=(e,t,r)=>ae(t,r)(e);N.any=N.isMatch;N.not=(e,t,r={})=>{t=[].concat(t).map(String);let n=new Set,s=[],a=o=>{r.onResult&&r.onResult(o),s.push(o.output)},i=N(e,t,F(I({},r),{onResult:a}));for(let o of s)i.includes(o)||n.add(o);return[...n]};N.contains=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);if(Array.isArray(t))return t.some(n=>N.contains(e,n,r));if(typeof t=="string"){if(fr(e)||fr(t))return!1;if(e.includes(t)||e.startsWith("./")&&e.slice(2).includes(t))return!0}return N.isMatch(e,t,F(I({},r),{contains:!0}))};N.matchKeys=(e,t,r)=>{if(!ze.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),t,r),s={};for(let a of n)s[a]=e[a];return s};N.some=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=ae(String(s),r);if(n.some(i=>a(i)))return!0}return!1};N.every=(e,t,r)=>{let n=[].concat(e);for(let s of[].concat(t)){let a=ae(String(s),r);if(!n.every(i=>a(i)))return!1}return!0};N.all=(e,t,r)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${lr.inspect(e)}"`);return[].concat(t).every(n=>ae(n,r)(e))};N.capture=(e,t,r)=>{let n=ze.isWindows(r),a=ae.makeRe(String(e),F(I({},r),{capture:!0})).exec(n?ze.toPosixSlashes(t):t);if(a)return a.slice(1).map(i=>i===void 0?"":i)};N.makeRe=(...e)=>ae.makeRe(...e);N.scan=(...e)=>ae.scan(...e);N.parse=(e,t)=>{let r=[];for(let n of[].concat(e||[]))for(let s of pr(String(n),t))r.push(ae.parse(s,t));return r};N.braces=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return t&&t.nobrace===!0||!/\{.*\}/.test(e)?[e]:pr(e,t)};N.braceExpand=(e,t)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,F(I({},t),{expand:!0}))};cr.exports=N});var gr=K((Es,dr)=>{"use strict";dr.exports=(e,...t)=>new Promise(r=>{r(e(...t))})});var Ar=K((xs,Ve)=>{"use strict";var Yn=gr(),mr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let t=[],r=0,n=()=>{r--,t.length>0&&t.shift()()},s=(o,h,...m)=>{r++;let f=Yn(o,...m);h(f),f.then(n,n)},a=(o,h,...m)=>{rnew Promise(m=>a(o,m,...h));return Object.defineProperties(i,{activeCount:{get:()=>r},pendingCount:{get:()=>t.length}}),i};Ve.exports=mr;Ve.exports.default=mr});var Vn={};Or(Vn,{default:()=>es});var ve=Q(require("@yarnpkg/cli")),ne=Q(require("@yarnpkg/core")),rt=Q(require("@yarnpkg/core")),le=Q(require("clipanion")),me=class extends ve.BaseCommand{constructor(){super(...arguments);this.json=le.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=le.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=le.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=le.Option.Rest()}async execute(){let t=await ne.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await ne.Project.find(t,this.context.cwd),s=await ne.Cache.find(t);await r.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(r.workspaces);else if(this.workspaces.length===0){if(!n)throw new ve.WorkspaceRequiredError(r.cwd,this.context.cwd);a=new Set([n])}else a=new Set(this.workspaces.map(o=>r.getWorkspaceByIdent(rt.structUtils.parseIdent(o))));for(let o of a)for(let h of this.production?["dependencies"]:ne.Manifest.hardDependencies)for(let m of o.manifest.getForScope(h).values()){let f=r.tryWorkspaceByDescriptor(m);f!==null&&a.add(f)}for(let o of r.workspaces)a.has(o)?this.production&&o.manifest.devDependencies.clear():(o.manifest.installConfig=o.manifest.installConfig||{},o.manifest.installConfig.selfReferences=!1,o.manifest.dependencies.clear(),o.manifest.devDependencies.clear(),o.manifest.peerDependencies.clear(),o.manifest.scripts.clear());return(await ne.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async o=>{await r.install({cache:s,report:o,persistProject:!1})})).exitCode()}};me.paths=[["workspaces","focus"]],me.usage=le.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var nt=me;var Ne=Q(require("@yarnpkg/cli")),Ie=Q(require("@yarnpkg/core")),be=Q(require("@yarnpkg/core")),Z=Q(require("@yarnpkg/core")),Rr=Q(require("@yarnpkg/plugin-git")),G=Q(require("clipanion")),Be=Q(hr()),yr=Q(require("os")),_r=Q(Ar()),te=Q(require("typanion")),Ee=class extends Ne.BaseCommand{constructor(){super(...arguments);this.recursive=G.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=G.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=G.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=G.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=G.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=G.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=G.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:te.isOneOf([te.isEnum(["unlimited"]),te.applyCascade(te.isNumber(),[te.isInteger(),te.isAtLeast(1)])])});this.topological=G.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=G.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=G.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=G.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=G.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=G.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=G.Option.String();this.args=G.Option.Proxy()}async execute(){let t=await Ie.Configuration.find(this.context.cwd,this.context.plugins),{project:r,workspace:n}=await Ie.Project.find(t,this.context.cwd);if(!this.all&&!n)throw new Ne.WorkspaceRequiredError(r.cwd,this.context.cwd);let s=this.cli.process([this.commandName,...this.args]),a=s.path.length===1&&s.path[0]==="run"&&typeof s.scriptName!="undefined"?s.scriptName:null;if(s.path.length===0)throw new G.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let i=this.all?r.topLevelWorkspace:n,o=this.since?Array.from(await Rr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:r})):[i,...this.from.length>0?i.getRecursiveWorkspaceChildren():[]],h=g=>Be.default.isMatch(Z.structUtils.stringifyIdent(g.locator),this.from),m=this.from.length>0?o.filter(h):o,f=new Set([...m,...m.map(g=>[...this.recursive?this.since?g.getRecursiveWorkspaceDependents():g.getRecursiveWorkspaceDependencies():g.getRecursiveWorkspaceChildren()]).flat()]),R=[],p=!1;if(a==null?void 0:a.includes(":")){for(let g of r.workspaces)if(g.manifest.scripts.has(a)&&(p=!p,p===!1))break}for(let g of f)a&&!g.manifest.scripts.has(a)&&!p||a===process.env.npm_lifecycle_event&&g.cwd===n.cwd||this.include.length>0&&!Be.default.isMatch(Z.structUtils.stringifyIdent(g.locator),this.include)||this.exclude.length>0&&Be.default.isMatch(Z.structUtils.stringifyIdent(g.locator),this.exclude)||this.publicOnly&&g.manifest.private===!0||R.push(g);let v=this.parallel?this.jobs==="unlimited"?Infinity:this.jobs||Math.max(1,(0,yr.cpus)().length/2):1,_=v===1?!1:this.parallel,y=_?this.interlaced:!0,b=(0,_r.default)(v),E=new Map,H=new Set,L=0,k=null,J=!1,ie=await be.StreamReport.start({configuration:t,stdout:this.context.stdout},async g=>{let w=async(D,{commandIndex:W})=>{if(J)return-1;!_&&this.verbose&&W>1&&g.reportSeparator();let l=zn(D,{configuration:t,verbose:this.verbose,commandIndex:W}),[x,T]=br(g,{prefix:l,interlaced:y}),[U,u]=br(g,{prefix:l,interlaced:y});try{this.verbose&&g.reportInfo(null,`${l} Process started`);let c=Date.now(),$=await this.cli.run([this.commandName,...this.args],{cwd:D.cwd,stdout:x,stderr:U})||0;x.end(),U.end(),await T,await u;let B=Date.now();if(this.verbose){let Y=t.get("enableTimers")?`, completed in ${Z.formatUtils.pretty(t,B-c,Z.formatUtils.Type.DURATION)}`:"";g.reportInfo(null,`${l} Process exited (exit code ${$})${Y}`)}return $===130&&(J=!0,k=$),$}catch(c){throw x.end(),U.end(),await T,await u,c}};for(let D of R)E.set(D.anchoredLocator.locatorHash,D);for(;E.size>0&&!g.hasErrors();){let D=[];for(let[x,T]of E){if(H.has(T.anchoredDescriptor.descriptorHash))continue;let U=!0;if(this.topological||this.topologicalDev){let u=this.topologicalDev?new Map([...T.manifest.dependencies,...T.manifest.devDependencies]):T.manifest.dependencies;for(let c of u.values()){let $=r.tryWorkspaceByDescriptor(c);if(U=$===null||!E.has($.anchoredLocator.locatorHash),!U)break}}if(!!U&&(H.add(T.anchoredDescriptor.descriptorHash),D.push(b(async()=>{let u=await w(T,{commandIndex:++L});return E.delete(x),H.delete(T.anchoredDescriptor.descriptorHash),u})),!_))break}if(D.length===0){let x=Array.from(E.values()).map(T=>Z.structUtils.prettyLocator(t,T.anchoredLocator)).join(", ");g.reportError(be.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${x})`);return}let l=(await Promise.all(D)).find(x=>x!==0);k===null&&(k=typeof l!="undefined"?1:k),(this.topological||this.topologicalDev)&&typeof l!="undefined"&&g.reportError(be.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return k!==null?k:ie.exitCode()}};Ee.paths=[["workspaces","foreach"]],Ee.usage=G.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});var Er=Ee;function br(e,{prefix:t,interlaced:r}){let n=e.createStreamReporter(t),s=new Z.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let a=new Promise(o=>{n.on("finish",()=>{o(s.active)})});if(r)return[s,a];let i=new Z.miscUtils.BufferStream;return i.pipe(s,{end:!1}),i.on("finish",()=>{s.end()}),[i,a]}function zn(e,{configuration:t,commandIndex:r,verbose:n}){if(!n)return null;let s=Z.structUtils.convertToIdent(e.locator),i=`[${Z.structUtils.stringifyIdent(s)}]:`,o=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],h=o[r%o.length];return Z.formatUtils.pretty(t,i,h)}var Jn={commands:[nt,Er]},es=Jn;return Vn;})(); 8 | /*! 9 | * fill-range 10 | * 11 | * Copyright (c) 2014-present, Jon Schlinkert. 12 | * Licensed under the MIT License. 13 | */ 14 | /*! 15 | * is-number 16 | * 17 | * Copyright (c) 2014-present, Jon Schlinkert. 18 | * Released under the MIT License. 19 | */ 20 | /*! 21 | * to-regex-range 22 | * 23 | * Copyright (c) 2015-present, Jon Schlinkert. 24 | * Released under the MIT License. 25 | */ 26 | return plugin; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | logFilters: 2 | - code: YN0076 3 | level: discard 4 | 5 | nodeLinker: node-modules 6 | 7 | plugins: 8 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 9 | spec: '@yarnpkg/plugin-workspace-tools' 10 | - path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs 11 | spec: '@yarnpkg/plugin-interactive-tools' 12 | 13 | yarnPath: .yarn/releases/yarn-3.2.1.cjs 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2021 François Farge, Adrien Cacciaguerra 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swarmion example - ⚠ Deprecated, check https://github.com/swarmion/swarmion instead 2 | 3 | 4 | 5 | [![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors-) 6 | 7 | 8 | 9 | This template is an example of a project generated with [Swarmion](https://github.com/swarmion/swarmion). 10 | 11 | ## Generate a new project with Swarmion 12 | 13 | ```bash 14 | yarn create swarmion-app 15 | ``` 16 | 17 | Then head to the Swarmion documentation on [swarmion.dev](https://www.swarmion.dev) 18 | 19 | ## Contributors ✨ 20 | 21 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |

François Farge

🚇 💻 🤔 📖

Adrien Cacciaguerra

💻 🤔 🚇

Louis Pinsard

💻

guillaumeduboc

💻

Maxime Vivier

💻

Guillaume Lagrange

💻

Pierre Milliotte

💻

Thomas Aribart

📖

Charles Géry

💻

Stan Hannebelle

💻

Quentin Hello

🚇

Paul Molin

💻
44 | 45 | 46 | 47 | 48 | 49 | 50 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 51 | -------------------------------------------------------------------------------- /backend/core/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig(); 4 | -------------------------------------------------------------------------------- /backend/core/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /backend/core/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /backend/core/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /backend/core/functions/health/config.ts: -------------------------------------------------------------------------------- 1 | import { getHandlerPath, LambdaFunction } from '@swarmion/serverless-helpers'; 2 | 3 | const config: LambdaFunction = { 4 | environment: {}, 5 | handler: getHandlerPath(__dirname), 6 | events: [ 7 | { 8 | httpApi: { 9 | method: 'get', 10 | path: '/health', 11 | }, 12 | }, 13 | ], 14 | }; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /backend/core/functions/health/handler.mock.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /backend/core/functions/health/handler.ts: -------------------------------------------------------------------------------- 1 | export const main = async (): Promise => { 2 | await Promise.resolve(); 3 | 4 | return 'ok'; 5 | }; 6 | -------------------------------------------------------------------------------- /backend/core/functions/index.ts: -------------------------------------------------------------------------------- 1 | import health from './health/config'; 2 | 3 | export const functions = { health }; 4 | -------------------------------------------------------------------------------- /backend/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/backend-core", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "scripts": { 7 | "deploy": "serverless deploy", 8 | "deploy-production": "serverless deploy --stage production", 9 | "deploy-staging": "serverless deploy --stage staging", 10 | "deployedCommit": "serverless deployedCommit", 11 | "lint-fix": "yarn linter-base-config --fix", 12 | "lint-fix-all": "yarn lint-fix .", 13 | "linter-base-config": "eslint --ext=js,ts .", 14 | "remove": "serverless remove", 15 | "remove-production": "serverless remove --stage production", 16 | "remove-staging": "serverless remove --stage staging", 17 | "sls-info": "serverless info --verbose", 18 | "test": "yarn test-linter && yarn test-type && yarn test-unit && yarn test-circular", 19 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js .", 20 | "test-linter": "yarn linter-base-config .", 21 | "test-type": "tsc --noEmit", 22 | "test-unit": "vitest run --coverage --passWithNoTests" 23 | }, 24 | "dependencies": { 25 | "@swarmion-starter/serverless-configuration": "1.0.0", 26 | "@swarmion/serverless-helpers": "0.8.3" 27 | }, 28 | "devDependencies": { 29 | "@serverless/typescript": "^3.19.0", 30 | "@swarmion-starter/core-contracts": "1.0.0", 31 | "@types/node": "^17.0.45", 32 | "@vitest/coverage-c8": "^0.23.1", 33 | "dependency-cruiser": "^11.10.0", 34 | "esbuild": "^0.14.47", 35 | "eslint": "^8.18.0", 36 | "serverless": "^3.19.0", 37 | "serverless-esbuild": "^1.30.0", 38 | "ts-node": "^10.8.1", 39 | "typescript": "^4.7.4", 40 | "vitest": "^0.23.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /backend/core/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "backend/core", 3 | "projectType": "application", 4 | "tags": [], 5 | "implicitDependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /backend/core/serverless.test.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from '@serverless/typescript'; 2 | import { testFunctionNames } from '@swarmion/serverless-helpers'; 3 | 4 | import * as sc from './serverless'; 5 | 6 | const serverlessConfiguration = sc as AWS; 7 | 8 | /** 9 | * serverless tests 10 | */ 11 | describe('root service serverless.ts', () => { 12 | testFunctionNames(serverlessConfiguration); 13 | }); 14 | -------------------------------------------------------------------------------- /backend/core/serverless.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from '@serverless/typescript'; 2 | import { mergeStageParams } from '@swarmion/serverless-helpers'; 3 | 4 | import { httpApiResourceContract } from '@swarmion-starter/core-contracts'; 5 | import { 6 | frameworkVersion, 7 | projectName, 8 | sharedEsbuildConfig, 9 | sharedParams, 10 | sharedProviderConfig, 11 | } from '@swarmion-starter/serverless-configuration'; 12 | 13 | import { functions } from './functions'; 14 | 15 | const serverlessConfiguration: AWS = { 16 | service: `${projectName}-core`, // Keep it short to have role name below 64 17 | frameworkVersion, 18 | configValidationMode: 'error', 19 | plugins: ['serverless-esbuild'], 20 | provider: { 21 | ...sharedProviderConfig, 22 | httpApi: { 23 | payload: '2.0', 24 | cors: { 25 | // @ts-expect-error we use a configuration per environment so we put it as a serverless variable 26 | allowedOrigins: '${param:apiGatewayCorsAllowedOrigins}', 27 | allowedHeaders: ['Content-Type', 'Authorization', 'Origin'], 28 | allowedMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], 29 | allowCredentials: true, 30 | }, 31 | metrics: true, 32 | }, 33 | }, 34 | functions, 35 | package: { individually: true }, 36 | params: mergeStageParams(sharedParams, { 37 | dev: { 38 | apiGatewayCorsAllowedOrigins: ['http://localhost:3000'], 39 | }, 40 | staging: { 41 | apiGatewayCorsAllowedOrigins: ['https://staging.my-domain.com'], 42 | }, 43 | production: { 44 | apiGatewayCorsAllowedOrigins: ['https://www.my-domain.com'], 45 | }, 46 | }), 47 | custom: { 48 | projectName, 49 | esbuild: sharedEsbuildConfig, 50 | }, 51 | resources: { 52 | Description: 'Core service', 53 | Outputs: { 54 | HttpApiId: httpApiResourceContract.exportValue({ 55 | description: 'The shared httpApi resource', 56 | value: { Ref: 'HttpApi' }, 57 | }), 58 | }, 59 | }, 60 | }; 61 | 62 | module.exports = serverlessConfiguration; 63 | -------------------------------------------------------------------------------- /backend/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "preserveSymlinks": true, 5 | "baseUrl": ".", 6 | "esModuleInterop": true 7 | }, 8 | "references": [ 9 | { "path": "../../contracts/core-contracts/tsconfig.build.json" }, 10 | { "path": "../../packages/serverless-configuration/tsconfig.build.json" } 11 | ], 12 | "include": ["./**/*.ts"], 13 | "ts-node": { 14 | "files": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /backend/core/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /backend/forum/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig(); 4 | -------------------------------------------------------------------------------- /backend/forum/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /backend/forum/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /backend/forum/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /backend/forum/functions/createPost/config.ts: -------------------------------------------------------------------------------- 1 | import { getTrigger } from '@swarmion/serverless-contracts'; 2 | import { getHandlerPath, LambdaFunction } from '@swarmion/serverless-helpers'; 3 | 4 | import { createPostContract } from '@swarmion-starter/forum-contracts'; 5 | 6 | const config: LambdaFunction = { 7 | environment: {}, 8 | handler: getHandlerPath(__dirname), 9 | events: [getTrigger(createPostContract)], 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /backend/forum/functions/createPost/handler.mock.json: -------------------------------------------------------------------------------- 1 | { 2 | "pathParameters": { "threadId": "fooBar" }, 3 | "body": { "content": "Hello, I am Swarmion!" } 4 | } 5 | -------------------------------------------------------------------------------- /backend/forum/functions/createPost/handler.test.ts: -------------------------------------------------------------------------------- 1 | import { handler } from './handler'; 2 | 3 | describe('createPost handler', () => { 4 | it('should return a post with the created content', async () => { 5 | const postContent = 'Hello from Swarmion'; 6 | 7 | const { content } = await handler({ 8 | pathParameters: { threadId: 'blob' }, 9 | body: { content: postContent }, 10 | requestContext: {}, 11 | }); 12 | 13 | expect(content).toEqual(postContent); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /backend/forum/functions/createPost/handler.ts: -------------------------------------------------------------------------------- 1 | import { getLambdaHandler } from '@swarmion/serverless-contracts'; 2 | import { applyHttpMiddlewares } from '@swarmion/serverless-helpers'; 3 | 4 | import { createPostContract } from '@swarmion-starter/forum-contracts'; 5 | 6 | export const handler = getLambdaHandler(createPostContract)(async event => { 7 | const { threadId } = event.pathParameters; 8 | const { content } = event.body; 9 | 10 | await Promise.resolve({ threadId }); 11 | 12 | return { 13 | id: 'myFirstPost', 14 | createdAt: '2021-10-25T12:12:00Z', 15 | editedAt: null, 16 | content, 17 | authorId: 'author2', 18 | }; 19 | }); 20 | 21 | export const main = applyHttpMiddlewares(handler, { 22 | inputSchema: createPostContract.inputSchema, 23 | }); 24 | -------------------------------------------------------------------------------- /backend/forum/functions/getThreadAndPosts/config.ts: -------------------------------------------------------------------------------- 1 | import { getTrigger } from '@swarmion/serverless-contracts'; 2 | import { getHandlerPath, LambdaFunction } from '@swarmion/serverless-helpers'; 3 | 4 | import { getThreadWithPostsContract } from '@swarmion-starter/forum-contracts'; 5 | 6 | const config: LambdaFunction = { 7 | environment: {}, 8 | handler: getHandlerPath(__dirname), 9 | events: [getTrigger(getThreadWithPostsContract)], 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /backend/forum/functions/getThreadAndPosts/handler.mock.json: -------------------------------------------------------------------------------- 1 | { "pathParameters": { "threadId": "fooBar" } } 2 | -------------------------------------------------------------------------------- /backend/forum/functions/getThreadAndPosts/handler.ts: -------------------------------------------------------------------------------- 1 | import { getLambdaHandler } from '@swarmion/serverless-contracts'; 2 | import { applyHttpMiddlewares } from '@swarmion/serverless-helpers'; 3 | 4 | import { getThreadWithPostsContract } from '@swarmion-starter/forum-contracts'; 5 | 6 | export const handler = getLambdaHandler(getThreadWithPostsContract)( 7 | async event => { 8 | const { threadId } = event.pathParameters; 9 | 10 | await Promise.resolve({ threadId }); 11 | 12 | return { 13 | thread: { 14 | id: threadId, 15 | name: 'My thread!', 16 | createdAt: '2021-10-25T12:12:00Z', 17 | editedAt: null, 18 | }, 19 | posts: [ 20 | { 21 | id: 'myFirstPost', 22 | createdAt: '2021-10-25T12:12:00Z', 23 | editedAt: null, 24 | content: 'Hello from my super forum!', 25 | authorId: 'author1', 26 | }, 27 | { 28 | id: 'myFirstPost', 29 | 30 | createdAt: '2021-10-25T12:12:00Z', 31 | editedAt: null, 32 | content: 'Wow this is cool', 33 | authorId: 'author2', 34 | }, 35 | ], 36 | }; 37 | }, 38 | ); 39 | 40 | export const main = applyHttpMiddlewares(handler, { 41 | inputSchema: getThreadWithPostsContract.inputSchema, 42 | }); 43 | -------------------------------------------------------------------------------- /backend/forum/functions/index.ts: -------------------------------------------------------------------------------- 1 | import createPost from './createPost/config'; 2 | import getThreadAndPosts from './getThreadAndPosts/config'; 3 | 4 | export const functions = { getThreadAndPosts, createPost }; 5 | -------------------------------------------------------------------------------- /backend/forum/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/backend-forum", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "scripts": { 7 | "deploy": "serverless deploy", 8 | "deploy-production": "serverless deploy --stage production", 9 | "deploy-staging": "serverless deploy --stage staging", 10 | "deployedCommit": "serverless deployedCommit", 11 | "lint-fix": "yarn linter-base-config --fix", 12 | "lint-fix-all": "yarn lint-fix .", 13 | "linter-base-config": "eslint --ext=js,ts .", 14 | "remove": "serverless remove", 15 | "remove-production": "serverless remove --stage production", 16 | "remove-staging": "serverless remove --stage staging", 17 | "sls-info": "serverless info --verbose", 18 | "test": "yarn test-linter && yarn test-type && yarn test-unit && yarn test-circular", 19 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js .", 20 | "test-linter": "yarn linter-base-config .", 21 | "test-type": "tsc --noEmit", 22 | "test-unit": "vitest run --coverage --passWithNoTests" 23 | }, 24 | "dependencies": { 25 | "@swarmion-starter/core-contracts": "1.0.0", 26 | "@swarmion-starter/forum-contracts": "1.0.0", 27 | "@swarmion-starter/serverless-configuration": "1.0.0", 28 | "@swarmion/serverless-contracts": "0.8.3", 29 | "@swarmion/serverless-helpers": "0.8.3", 30 | "aws-sdk": "^2.1160.0", 31 | "http-errors": "^2.0.0" 32 | }, 33 | "devDependencies": { 34 | "@serverless/typescript": "^3.19.0", 35 | "@types/http-errors": "^1.8.2", 36 | "@types/node": "^17.0.45", 37 | "@vitest/coverage-c8": "^0.23.1", 38 | "dependency-cruiser": "^11.10.0", 39 | "esbuild": "^0.14.47", 40 | "eslint": "^8.18.0", 41 | "serverless": "^3.19.0", 42 | "serverless-esbuild": "^1.30.0", 43 | "serverless-iam-roles-per-function": "^3.2.0", 44 | "ts-node": "^10.8.1", 45 | "typescript": "^4.7.4", 46 | "vitest": "^0.23.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /backend/forum/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "backend/forum", 3 | "projectType": "application", 4 | "tags": [], 5 | "implicitDependencies": ["backend-core"] 6 | } 7 | -------------------------------------------------------------------------------- /backend/forum/serverless.test.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from '@serverless/typescript'; 2 | import { testFunctionNames } from '@swarmion/serverless-helpers'; 3 | 4 | import * as sc from './serverless'; 5 | 6 | const serverlessConfiguration = sc as AWS; 7 | 8 | /** 9 | * serverless tests 10 | */ 11 | describe('root service serverless.ts', () => { 12 | testFunctionNames(serverlessConfiguration); 13 | }); 14 | -------------------------------------------------------------------------------- /backend/forum/serverless.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from '@serverless/typescript'; 2 | 3 | import { httpApiResourceContract } from '@swarmion-starter/core-contracts'; 4 | import { 5 | frameworkVersion, 6 | projectName, 7 | sharedEsbuildConfig, 8 | sharedParams, 9 | sharedProviderConfig, 10 | } from '@swarmion-starter/serverless-configuration'; 11 | 12 | import { functions } from './functions'; 13 | 14 | const serverlessConfiguration: AWS = { 15 | service: `${projectName}-forum`, // Keep it short to have role name below 64 16 | frameworkVersion, 17 | configValidationMode: 'error', 18 | plugins: ['serverless-esbuild', 'serverless-iam-roles-per-function'], 19 | provider: { 20 | ...sharedProviderConfig, 21 | httpApi: { 22 | id: httpApiResourceContract.importValue, 23 | }, 24 | }, 25 | params: sharedParams, 26 | functions, 27 | package: { individually: true }, 28 | custom: { 29 | projectName, 30 | esbuild: sharedEsbuildConfig, 31 | }, 32 | resources: { 33 | Description: 'Forum service: handle forum activity, posts and threads', 34 | }, 35 | }; 36 | 37 | module.exports = serverlessConfiguration; 38 | -------------------------------------------------------------------------------- /backend/forum/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "preserveSymlinks": true, 5 | "baseUrl": ".", 6 | "esModuleInterop": true 7 | }, 8 | "references": [ 9 | { "path": "../../packages/serverless-configuration/tsconfig.build.json" }, 10 | { "path": "../../contracts/forum-contracts/tsconfig.build.json" }, 11 | { "path": "../../contracts/core-contracts/tsconfig.build.json" } 12 | ], 13 | "include": ["./**/*.ts"], 14 | "ts-node": { 15 | "files": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/forum/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /backend/users/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig(); 4 | -------------------------------------------------------------------------------- /backend/users/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /backend/users/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /backend/users/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /backend/users/functions/getUser/config.ts: -------------------------------------------------------------------------------- 1 | import { getTrigger } from '@swarmion/serverless-contracts'; 2 | import { getHandlerPath, LambdaFunction } from '@swarmion/serverless-helpers'; 3 | 4 | import { getUserContract } from '@swarmion-starter/users-contracts'; 5 | 6 | const config: LambdaFunction = { 7 | environment: {}, 8 | handler: getHandlerPath(__dirname), 9 | events: [getTrigger(getUserContract)], 10 | }; 11 | 12 | export default config; 13 | -------------------------------------------------------------------------------- /backend/users/functions/getUser/handler.mock.json: -------------------------------------------------------------------------------- 1 | { "pathParameters": { "userId": "fooBar" } } 2 | -------------------------------------------------------------------------------- /backend/users/functions/getUser/handler.ts: -------------------------------------------------------------------------------- 1 | import { getLambdaHandler } from '@swarmion/serverless-contracts'; 2 | import { applyHttpMiddlewares } from '@swarmion/serverless-helpers'; 3 | 4 | import { getUserContract } from '@swarmion-starter/users-contracts'; 5 | 6 | const handler = getLambdaHandler(getUserContract)(async event => { 7 | const { userId } = event.pathParameters; 8 | 9 | await Promise.resolve({ userId }); 10 | 11 | return { userId, userName: 'hello_world' }; 12 | }); 13 | 14 | export const main = applyHttpMiddlewares(handler, { 15 | inputSchema: getUserContract.inputSchema, 16 | outputSchema: getUserContract.outputSchema, 17 | }); 18 | -------------------------------------------------------------------------------- /backend/users/functions/index.ts: -------------------------------------------------------------------------------- 1 | import getUser from './getUser/config'; 2 | 3 | export const functions = { getUser }; 4 | -------------------------------------------------------------------------------- /backend/users/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/backend-users", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "scripts": { 7 | "deploy": "serverless deploy", 8 | "deploy-production": "serverless deploy --stage production", 9 | "deploy-staging": "serverless deploy --stage staging", 10 | "deployedCommit": "serverless deployedCommit", 11 | "lint-fix": "yarn linter-base-config --fix", 12 | "lint-fix-all": "yarn lint-fix .", 13 | "linter-base-config": "eslint --ext=js,ts .", 14 | "remove": "serverless remove", 15 | "remove-production": "serverless remove --stage production", 16 | "remove-staging": "serverless remove --stage staging", 17 | "sls-info": "serverless info --verbose", 18 | "test": "yarn test-linter && yarn test-type && yarn test-unit && yarn test-circular", 19 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js .", 20 | "test-linter": "yarn linter-base-config .", 21 | "test-type": "tsc --noEmit", 22 | "test-unit": "vitest run --coverage --passWithNoTests" 23 | }, 24 | "dependencies": { 25 | "@swarmion-starter/core-contracts": "1.0.0", 26 | "@swarmion-starter/serverless-configuration": "1.0.0", 27 | "@swarmion-starter/users-contracts": "1.0.0", 28 | "@swarmion/serverless-contracts": "0.8.3", 29 | "@swarmion/serverless-helpers": "0.8.3", 30 | "aws-sdk": "^2.1160.0", 31 | "http-errors": "^2.0.0" 32 | }, 33 | "devDependencies": { 34 | "@serverless/typescript": "^3.19.0", 35 | "@types/http-errors": "^1.8.2", 36 | "@types/node": "^17.0.45", 37 | "@vitest/coverage-c8": "^0.23.1", 38 | "dependency-cruiser": "^11.10.0", 39 | "esbuild": "^0.14.47", 40 | "eslint": "^8.18.0", 41 | "serverless": "^3.19.0", 42 | "serverless-esbuild": "^1.30.0", 43 | "serverless-iam-roles-per-function": "^3.2.0", 44 | "ts-node": "^10.8.1", 45 | "typescript": "^4.7.4", 46 | "vitest": "^0.23.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /backend/users/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "backend/users", 3 | "projectType": "application", 4 | "tags": [], 5 | "implicitDependencies": ["backend-core"] 6 | } 7 | -------------------------------------------------------------------------------- /backend/users/serverless.test.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from '@serverless/typescript'; 2 | import { testFunctionNames } from '@swarmion/serverless-helpers'; 3 | 4 | import * as sc from './serverless'; 5 | 6 | const serverlessConfiguration = sc as AWS; 7 | 8 | /** 9 | * serverless tests 10 | */ 11 | describe('root service serverless.ts', () => { 12 | testFunctionNames(serverlessConfiguration); 13 | }); 14 | -------------------------------------------------------------------------------- /backend/users/serverless.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from '@serverless/typescript'; 2 | 3 | import { httpApiResourceContract } from '@swarmion-starter/core-contracts'; 4 | import { 5 | frameworkVersion, 6 | projectName, 7 | sharedEsbuildConfig, 8 | sharedParams, 9 | sharedProviderConfig, 10 | } from '@swarmion-starter/serverless-configuration'; 11 | 12 | import { functions } from './functions'; 13 | 14 | const serverlessConfiguration: AWS = { 15 | service: `${projectName}-users`, // Keep it short to have role name below 64 16 | frameworkVersion, 17 | configValidationMode: 'error', 18 | plugins: ['serverless-esbuild', 'serverless-iam-roles-per-function'], 19 | provider: { 20 | ...sharedProviderConfig, 21 | httpApi: { 22 | id: httpApiResourceContract.importValue, 23 | }, 24 | }, 25 | params: sharedParams, 26 | functions, 27 | package: { individually: true }, 28 | custom: { 29 | projectName, 30 | esbuild: sharedEsbuildConfig, 31 | }, 32 | resources: { 33 | Description: 'Users service: manage users', 34 | }, 35 | }; 36 | 37 | module.exports = serverlessConfiguration; 38 | -------------------------------------------------------------------------------- /backend/users/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "preserveSymlinks": true, 5 | "baseUrl": ".", 6 | "esModuleInterop": true 7 | }, 8 | "references": [ 9 | { "path": "../../packages/serverless-configuration/tsconfig.build.json" }, 10 | { "path": "../../contracts/core-contracts/tsconfig.build.json" }, 11 | { "path": "../../contracts/users-contracts/tsconfig.build.json" } 12 | ], 13 | "include": ["./**/*.ts"], 14 | "ts-node": { 15 | "files": true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/users/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /commonConfiguration/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "inputs": [ 4 | { 5 | "id": "functionName", 6 | "description": "Enter the name of the function to test", 7 | "default": "health", 8 | "type": "promptString" 9 | } 10 | ], 11 | "configurations": [ 12 | { 13 | "name": "Debug a lambda function λ", 14 | "type": "node", 15 | "request": "launch", 16 | "cwd": "${workspaceFolder}", 17 | "runtimeExecutable": "yarn", 18 | "args": [ 19 | "serverless", 20 | "invoke", 21 | "local", 22 | "-f", 23 | "${input:functionName}", 24 | "--path", 25 | "functions/${input:functionName}/handler.mock.json" 26 | ], 27 | "sourceMaps": true, 28 | "smartStep": true, 29 | "outFiles": ["**/.esbuild/**/*.js"], 30 | "protocol": "inspector", 31 | "autoAttachChildProcesses": true, 32 | "console": "integratedTerminal", 33 | "outputCapture": "console" 34 | }, 35 | { 36 | "type": "pwa-node", 37 | "request": "launch", 38 | "name": "Debug Current Test File", 39 | "autoAttachChildProcesses": true, 40 | "skipFiles": ["/**", "**/node_modules/**"], 41 | "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", 42 | "args": ["run", "${relativeFile}"], 43 | "smartStep": true, 44 | "console": "integratedTerminal" 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /commonConfiguration/babel.config.js: -------------------------------------------------------------------------------- 1 | const defaultPresets = [ 2 | ['@babel/preset-typescript', { allowNamespaces: true }], 3 | ]; 4 | 5 | const defaultIgnores = [/.*\/(.*\.|)test\.tsx?/, /node_modules/, /dist/]; 6 | 7 | const defaultPlugins = [ 8 | [ 9 | 'module-resolver', 10 | { 11 | root: ['./src'], 12 | extensions: ['.ts', '.tsx'], 13 | }, 14 | ], 15 | '@babel/plugin-transform-runtime', 16 | ]; 17 | 18 | const presetsForESM = [ 19 | [ 20 | '@babel/preset-env', 21 | { 22 | modules: false, 23 | }, 24 | ], 25 | ...defaultPresets, 26 | ]; 27 | const presetsForCJS = [ 28 | [ 29 | '@babel/preset-env', 30 | { 31 | modules: 'cjs', 32 | }, 33 | ], 34 | ...defaultPresets, 35 | ]; 36 | 37 | module.exports = (plugins = [], presets = []) => { 38 | return { 39 | env: { 40 | cjs: { 41 | presets: [...presets, ...presetsForCJS], 42 | }, 43 | esm: { 44 | presets: [...presets, ...presetsForESM], 45 | }, 46 | }, 47 | ignore: defaultIgnores, 48 | plugins: [...plugins, ...defaultPlugins], 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /commonConfiguration/dependency-cruiser.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('dependency-cruiser').IConfiguration} */ 2 | module.exports = ({ pathNot, path } = { pathNot: [], path: [] }) => ({ 3 | forbidden: [ 4 | { 5 | name: 'no-circular', 6 | severity: 'error', 7 | comment: 8 | 'This dependency is part of a circular relationship. You might want to revise ' + 9 | 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', 10 | from: { pathNot, path }, 11 | to: { 12 | circular: true, 13 | }, 14 | }, 15 | ], 16 | options: { 17 | doNotFollow: { 18 | path: 'node_modules', 19 | dependencyTypes: [ 20 | 'npm', 21 | 'npm-dev', 22 | 'npm-optional', 23 | 'npm-peer', 24 | 'npm-bundled', 25 | 'npm-no-pkg', 26 | ], 27 | }, 28 | 29 | moduleSystems: ['amd', 'cjs', 'es6', 'tsd'], 30 | 31 | tsPreCompilationDeps: true, 32 | 33 | tsConfig: { 34 | fileName: 'tsconfig.json', 35 | }, 36 | 37 | enhancedResolveOptions: { 38 | exportsFields: ['exports'], 39 | 40 | conditionNames: ['import', 'require', 'node', 'default'], 41 | }, 42 | reporterOptions: { 43 | dot: { 44 | collapsePattern: 'node_modules/[^/]+', 45 | }, 46 | archi: { 47 | collapsePattern: 48 | '^(packages|src|lib|app|bin|test(s?)|spec(s?))/[^/]+|node_modules/[^/]+', 49 | }, 50 | }, 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /contracts/core-contracts/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig({ path: './src' }); 4 | -------------------------------------------------------------------------------- /contracts/core-contracts/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /contracts/core-contracts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /contracts/core-contracts/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /contracts/core-contracts/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /contracts/core-contracts/babel.config.js: -------------------------------------------------------------------------------- 1 | const commonBabelConfig = require('../../commonConfiguration/babel.config'); 2 | 3 | module.exports = commonBabelConfig(); 4 | -------------------------------------------------------------------------------- /contracts/core-contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/core-contracts", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "sideEffects": false, 7 | "files": [ 8 | "dist" 9 | ], 10 | "main": "dist/cjs/index.js", 11 | "module": "dist/esm/index.js", 12 | "types": "dist/types/index.d.ts", 13 | "contracts": true, 14 | "scripts": { 15 | "lint-fix": "yarn linter-base-config --fix", 16 | "lint-fix-all": "yarn lint-fix .", 17 | "linter-base-config": "eslint --ext=js,ts .", 18 | "package": "rm -rf dist && yarn package-cjs && yarn package-esm && yarn package-types", 19 | "package-cjs": "NODE_ENV=cjs yarn transpile --out-dir dist/cjs --source-maps", 20 | "package-esm": "NODE_ENV=esm yarn transpile --out-dir dist/esm --source-maps", 21 | "package-types": "ttsc -p tsconfig.build.json", 22 | "test": "yarn test-linter && yarn test-type && yarn test-circular", 23 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js src", 24 | "test-linter": "yarn linter-base-config .", 25 | "test-type": "tsc --noEmit --emitDeclarationOnly false", 26 | "transpile": "babel src --extensions .ts --quiet", 27 | "watch": "rm -rf dist && concurrently 'yarn:package-* --watch'" 28 | }, 29 | "dependencies": { 30 | "@babel/runtime": "^7.18.3", 31 | "@swarmion/serverless-contracts": "0.8.3" 32 | }, 33 | "devDependencies": { 34 | "@babel/cli": "^7.17.10", 35 | "@babel/core": "^7.18.5", 36 | "@babel/plugin-transform-runtime": "^7.18.5", 37 | "@babel/preset-env": "^7.18.2", 38 | "@babel/preset-typescript": "^7.17.12", 39 | "@types/node": "^17.0.45", 40 | "@vitest/coverage-c8": "^0.23.1", 41 | "@zerollup/ts-transform-paths": "^1.7.18", 42 | "babel-plugin-module-resolver": "^4.1.0", 43 | "concurrently": "^7.2.2", 44 | "dependency-cruiser": "^11.10.0", 45 | "eslint": "^8.18.0", 46 | "prettier": "^2.7.1", 47 | "ts-node": "^10.8.1", 48 | "ttypescript": "^1.5.13", 49 | "typescript": "^4.7.4", 50 | "vitest": "^0.23.1" 51 | }, 52 | "nx": { 53 | "targets": { 54 | "package": { 55 | "outputs": [ 56 | "contracts/core-contracts/dist" 57 | ] 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /contracts/core-contracts/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "contracts/core-contracts", 3 | "projectType": "library", 4 | "tags": [], 5 | "implicitDependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /contracts/core-contracts/src/contracts/cloudFormation.ts: -------------------------------------------------------------------------------- 1 | import { CloudFormationContract } from '@swarmion/serverless-contracts'; 2 | 3 | export const httpApiResourceContract = new CloudFormationContract({ 4 | id: 'core-httpApi', 5 | name: 'CoreHttpApi', 6 | }); 7 | -------------------------------------------------------------------------------- /contracts/core-contracts/src/contracts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cloudFormation'; 2 | -------------------------------------------------------------------------------- /contracts/core-contracts/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './contracts'; 2 | -------------------------------------------------------------------------------- /contracts/core-contracts/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "exclude": ["./vite*", "./**/*.test.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /contracts/core-contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "composite": true, 6 | "plugins": [{ "transform": "@zerollup/ts-transform-paths" }], 7 | "emitDeclarationOnly": true, 8 | "outDir": "./dist/types" 9 | }, 10 | "exclude": ["./dist"], 11 | "include": ["./**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /contracts/core-contracts/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /contracts/forum-contracts/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig({ path: './src' }); 4 | -------------------------------------------------------------------------------- /contracts/forum-contracts/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /contracts/forum-contracts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /contracts/forum-contracts/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /contracts/forum-contracts/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /contracts/forum-contracts/babel.config.js: -------------------------------------------------------------------------------- 1 | const commonBabelConfig = require('../../commonConfiguration/babel.config'); 2 | 3 | module.exports = commonBabelConfig(); 4 | -------------------------------------------------------------------------------- /contracts/forum-contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/forum-contracts", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "sideEffects": false, 7 | "files": [ 8 | "dist" 9 | ], 10 | "main": "dist/cjs/index.js", 11 | "module": "dist/esm/index.js", 12 | "types": "dist/types/index.d.ts", 13 | "contracts": true, 14 | "scripts": { 15 | "lint-fix": "yarn linter-base-config --fix", 16 | "lint-fix-all": "yarn lint-fix .", 17 | "linter-base-config": "eslint --ext=js,ts .", 18 | "package": "rm -rf dist && yarn package-cjs && yarn package-esm && yarn package-types", 19 | "package-cjs": "NODE_ENV=cjs yarn transpile --out-dir dist/cjs --source-maps", 20 | "package-esm": "NODE_ENV=esm yarn transpile --out-dir dist/esm --source-maps", 21 | "package-types": "ttsc -p tsconfig.build.json", 22 | "test": "yarn test-linter && yarn test-type && yarn test-circular", 23 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js src", 24 | "test-linter": "yarn linter-base-config .", 25 | "test-type": "tsc --noEmit --emitDeclarationOnly false", 26 | "transpile": "babel src --extensions .ts --quiet", 27 | "watch": "rm -rf dist && concurrently 'yarn:package-* --watch'" 28 | }, 29 | "dependencies": { 30 | "@babel/runtime": "^7.18.3", 31 | "@swarmion/serverless-contracts": "0.8.3" 32 | }, 33 | "devDependencies": { 34 | "@babel/cli": "^7.17.10", 35 | "@babel/core": "^7.18.5", 36 | "@babel/plugin-transform-runtime": "^7.18.5", 37 | "@babel/preset-env": "^7.18.2", 38 | "@babel/preset-typescript": "^7.17.12", 39 | "@types/node": "^17.0.45", 40 | "@vitest/coverage-c8": "^0.23.1", 41 | "@zerollup/ts-transform-paths": "^1.7.18", 42 | "babel-plugin-module-resolver": "^4.1.0", 43 | "concurrently": "^7.2.2", 44 | "dependency-cruiser": "^11.10.0", 45 | "eslint": "^8.18.0", 46 | "json-schema-to-ts": "^2.5.3", 47 | "prettier": "^2.7.1", 48 | "ts-node": "^10.8.1", 49 | "ttypescript": "^1.5.13", 50 | "typescript": "^4.7.4", 51 | "vitest": "^0.23.1" 52 | }, 53 | "nx": { 54 | "targets": { 55 | "package": { 56 | "outputs": [ 57 | "contracts/forum-contracts/dist" 58 | ] 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/forum-contracts/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "contracts/forum-contracts", 3 | "projectType": "library", 4 | "tags": [], 5 | "implicitDependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /contracts/forum-contracts/src/contracts/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './post'; 2 | export * from './thread'; 3 | -------------------------------------------------------------------------------- /contracts/forum-contracts/src/contracts/entities/post.ts: -------------------------------------------------------------------------------- 1 | import { FromSchema } from 'json-schema-to-ts'; 2 | 3 | export const postEntitySchema = { 4 | type: 'object', 5 | properties: { 6 | id: { type: 'string' }, 7 | content: { type: 'string' }, 8 | authorId: { type: 'string' }, 9 | createdAt: { type: 'string' }, 10 | editedAt: { type: ['string', 'null'] }, 11 | }, 12 | required: ['id', 'content', 'authorId', 'createdAt', 'editedAt'], 13 | additionalProperties: false, 14 | } as const; 15 | 16 | export type PostEntity = FromSchema; 17 | -------------------------------------------------------------------------------- /contracts/forum-contracts/src/contracts/entities/thread.ts: -------------------------------------------------------------------------------- 1 | import { FromSchema } from 'json-schema-to-ts'; 2 | 3 | export const threadEntitySchema = { 4 | type: 'object', 5 | properties: { 6 | id: { type: 'string' }, 7 | name: { type: 'string' }, 8 | createdAt: { type: 'string' }, 9 | editedAt: { type: ['string', 'null'] }, 10 | }, 11 | required: ['id', 'name', 'createdAt', 'editedAt'], 12 | additionalProperties: false, 13 | } as const; 14 | 15 | export type ThreadEntity = FromSchema; 16 | -------------------------------------------------------------------------------- /contracts/forum-contracts/src/contracts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './requests'; 2 | export * from './entities'; 3 | -------------------------------------------------------------------------------- /contracts/forum-contracts/src/contracts/requests/createPost.ts: -------------------------------------------------------------------------------- 1 | import { ApiGatewayContract } from '@swarmion/serverless-contracts'; 2 | 3 | import { postEntitySchema } from 'contracts/entities'; 4 | 5 | const pathParametersSchema = { 6 | type: 'object', 7 | properties: { 8 | threadId: { type: 'string' }, 9 | }, 10 | required: ['threadId'], 11 | additionalProperties: false, 12 | } as const; 13 | 14 | const bodySchema = { 15 | type: 'object', 16 | properties: { 17 | content: { type: 'string' }, 18 | }, 19 | required: ['content'], 20 | additionalProperties: false, 21 | } as const; 22 | 23 | export const createPostContract = new ApiGatewayContract({ 24 | id: 'forum-createPost', 25 | path: '/forum/thread/{threadId}', 26 | method: 'POST', 27 | integrationType: 'httpApi', 28 | pathParametersSchema, 29 | queryStringParametersSchema: undefined, 30 | bodySchema, 31 | headersSchema: undefined, 32 | outputSchema: postEntitySchema, 33 | authorizerType: undefined, 34 | }); 35 | -------------------------------------------------------------------------------- /contracts/forum-contracts/src/contracts/requests/getThreadWithPosts.ts: -------------------------------------------------------------------------------- 1 | import { ApiGatewayContract } from '@swarmion/serverless-contracts'; 2 | 3 | import { postEntitySchema, threadEntitySchema } from 'contracts/entities'; 4 | 5 | const pathParametersSchema = { 6 | type: 'object', 7 | properties: { 8 | threadId: { type: 'string' }, 9 | }, 10 | required: ['threadId'], 11 | additionalProperties: false, 12 | } as const; 13 | 14 | const outputSchema = { 15 | type: 'object', 16 | properties: { 17 | thread: threadEntitySchema, 18 | posts: { type: 'array', items: postEntitySchema }, 19 | }, 20 | required: ['thread', 'posts'], 21 | additionalProperties: false, 22 | } as const; 23 | 24 | export const getThreadWithPostsContract = new ApiGatewayContract({ 25 | id: 'forum-getThreadWithPosts', 26 | path: '/forum/thread/{threadId}', 27 | method: 'GET', 28 | integrationType: 'httpApi', 29 | pathParametersSchema, 30 | queryStringParametersSchema: undefined, 31 | bodySchema: undefined, 32 | headersSchema: undefined, 33 | outputSchema, 34 | authorizerType: undefined, 35 | }); 36 | -------------------------------------------------------------------------------- /contracts/forum-contracts/src/contracts/requests/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getThreadWithPosts'; 2 | export * from './createPost'; 3 | -------------------------------------------------------------------------------- /contracts/forum-contracts/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './contracts'; 2 | -------------------------------------------------------------------------------- /contracts/forum-contracts/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "exclude": ["./vite*", "./**/*.test.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /contracts/forum-contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "composite": true, 6 | "plugins": [{ "transform": "@zerollup/ts-transform-paths" }], 7 | "emitDeclarationOnly": true, 8 | "outDir": "./dist/types" 9 | }, 10 | "exclude": ["./dist"], 11 | "include": ["./**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /contracts/forum-contracts/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /contracts/users-contracts/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig({ path: './src' }); 4 | -------------------------------------------------------------------------------- /contracts/users-contracts/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /contracts/users-contracts/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /contracts/users-contracts/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /contracts/users-contracts/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /contracts/users-contracts/babel.config.js: -------------------------------------------------------------------------------- 1 | const commonBabelConfig = require('../../commonConfiguration/babel.config'); 2 | 3 | module.exports = commonBabelConfig(); 4 | -------------------------------------------------------------------------------- /contracts/users-contracts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/users-contracts", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "sideEffects": false, 7 | "files": [ 8 | "dist" 9 | ], 10 | "main": "dist/cjs/index.js", 11 | "module": "dist/esm/index.js", 12 | "types": "dist/types/index.d.ts", 13 | "contracts": true, 14 | "scripts": { 15 | "lint-fix": "yarn linter-base-config --fix", 16 | "lint-fix-all": "yarn lint-fix .", 17 | "linter-base-config": "eslint --ext=js,ts .", 18 | "package": "rm -rf dist && yarn package-cjs && yarn package-esm && yarn package-types", 19 | "package-cjs": "NODE_ENV=cjs yarn transpile --out-dir dist/cjs --source-maps", 20 | "package-esm": "NODE_ENV=esm yarn transpile --out-dir dist/esm --source-maps", 21 | "package-types": "ttsc -p tsconfig.build.json", 22 | "test": "yarn test-linter && yarn test-type && yarn test-circular", 23 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js src", 24 | "test-linter": "yarn linter-base-config .", 25 | "test-type": "tsc --noEmit --emitDeclarationOnly false", 26 | "transpile": "babel src --extensions .ts --quiet", 27 | "watch": "rm -rf dist && concurrently 'yarn:package-* --watch'" 28 | }, 29 | "dependencies": { 30 | "@babel/runtime": "^7.18.3", 31 | "@swarmion/serverless-contracts": "0.8.3" 32 | }, 33 | "devDependencies": { 34 | "@babel/cli": "^7.17.10", 35 | "@babel/core": "^7.18.5", 36 | "@babel/plugin-transform-runtime": "^7.18.5", 37 | "@babel/preset-env": "^7.18.2", 38 | "@babel/preset-typescript": "^7.17.12", 39 | "@types/node": "^17.0.45", 40 | "@vitest/coverage-c8": "^0.23.1", 41 | "@zerollup/ts-transform-paths": "^1.7.18", 42 | "babel-plugin-module-resolver": "^4.1.0", 43 | "concurrently": "^7.2.2", 44 | "dependency-cruiser": "^11.10.0", 45 | "eslint": "^8.18.0", 46 | "json-schema-to-ts": "^2.5.3", 47 | "prettier": "^2.7.1", 48 | "ts-node": "^10.8.1", 49 | "ttypescript": "^1.5.13", 50 | "typescript": "^4.7.4", 51 | "vitest": "^0.23.1" 52 | }, 53 | "nx": { 54 | "targets": { 55 | "package": { 56 | "outputs": [ 57 | "contracts/users-contracts/dist" 58 | ] 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /contracts/users-contracts/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "contracts/users-contracts", 3 | "projectType": "library", 4 | "tags": [], 5 | "implicitDependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /contracts/users-contracts/src/contracts/entities/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user'; 2 | -------------------------------------------------------------------------------- /contracts/users-contracts/src/contracts/entities/user.ts: -------------------------------------------------------------------------------- 1 | import { FromSchema } from 'json-schema-to-ts'; 2 | 3 | export const userEntitySchema = { 4 | type: 'object', 5 | properties: { 6 | userId: { type: 'string' }, 7 | userName: { type: 'string' }, 8 | }, 9 | required: ['userId', 'userName'], 10 | additionalProperties: false, 11 | } as const; 12 | 13 | export type UserEntity = FromSchema; 14 | -------------------------------------------------------------------------------- /contracts/users-contracts/src/contracts/index.ts: -------------------------------------------------------------------------------- 1 | export * from './entities'; 2 | export * from './requests'; 3 | -------------------------------------------------------------------------------- /contracts/users-contracts/src/contracts/requests/getUser.ts: -------------------------------------------------------------------------------- 1 | import { ApiGatewayContract } from '@swarmion/serverless-contracts'; 2 | 3 | import { userEntitySchema } from 'contracts/entities'; 4 | 5 | const pathParametersSchema = { 6 | type: 'object', 7 | properties: { 8 | userId: { type: 'string' }, 9 | }, 10 | required: ['userId'], 11 | additionalProperties: false, 12 | } as const; 13 | 14 | export const getUserContract = new ApiGatewayContract({ 15 | id: 'users-getUser', 16 | path: '/users/{userId}', 17 | method: 'GET', 18 | integrationType: 'httpApi', 19 | pathParametersSchema, 20 | queryStringParametersSchema: undefined, 21 | bodySchema: undefined, 22 | headersSchema: undefined, 23 | outputSchema: userEntitySchema, 24 | authorizerType: undefined, 25 | }); 26 | -------------------------------------------------------------------------------- /contracts/users-contracts/src/contracts/requests/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getUser'; 2 | -------------------------------------------------------------------------------- /contracts/users-contracts/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './contracts'; 2 | -------------------------------------------------------------------------------- /contracts/users-contracts/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "exclude": ["./vite*", "./**/*.test.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /contracts/users-contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "composite": true, 6 | "plugins": [{ "transform": "@zerollup/ts-transform-paths" }], 7 | "emitDeclarationOnly": true, 8 | "outDir": "./dist/types" 9 | }, 10 | "exclude": ["./dist"], 11 | "include": ["./**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /contracts/users-contracts/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /fresko.config.ts: -------------------------------------------------------------------------------- 1 | import { declareConfiguration } from 'fresko'; 2 | 3 | export default declareConfiguration({ 4 | prompts: [ 5 | // Every time the yarn.lock file is updated, fresko will offer to run `yarn install` 6 | { 7 | path: 'yarn.lock', 8 | command: 'yarn install', 9 | }, 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { browser: true }, 3 | extends: [ 4 | 'plugin:jsx-a11y/recommended', 5 | 'plugin:react/recommended', 6 | 'prettier', 7 | ], 8 | plugins: ['react-hooks', 'jsx-a11y', 'risxss'], 9 | rules: { 10 | 'react/prop-types': 'off', 11 | 'react-hooks/rules-of-hooks': 'error', 12 | 'react-hooks/exhaustive-deps': 'error', 13 | 'react/react-in-jsx-scope': 'off', 14 | 'react/self-closing-comp': 'error', 15 | 'jsx-a11y/anchor-is-valid': 'error', 16 | 'jsx-a11y/accessible-emoji': 'off', 17 | 'risxss/catch-potential-xss-react': 'error', 18 | 'react/jsx-curly-brace-presence': 'error', 19 | }, 20 | settings: { react: { version: 'detect' } }, 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/app/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig({}); 4 | -------------------------------------------------------------------------------- /frontend/app/.env.example: -------------------------------------------------------------------------------- 1 | VITE_API_URL= 2 | VITE_FRONT_URL=http://localhost:3000 3 | -------------------------------------------------------------------------------- /frontend/app/.env.production: -------------------------------------------------------------------------------- 1 | VITE_API_URL=https://api.example.com 2 | VITE_FRONT_URL=https://app.example.com 3 | -------------------------------------------------------------------------------- /frontend/app/.env.staging: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VITE_API_URL=https://api.staging.example.com 3 | VITE_FRONT_URL=https://app.staging.example.com 4 | -------------------------------------------------------------------------------- /frontend/app/.env.test: -------------------------------------------------------------------------------- 1 | VITE_API_URL=test.com 2 | VITE_FRONT_URL=test-front.com 3 | -------------------------------------------------------------------------------- /frontend/app/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /frontend/app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | bundles 7 | -------------------------------------------------------------------------------- /frontend/app/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | 3 | module.exports = { 4 | ...baseConfig, 5 | '*.{ts,tsx}': 'yarn stylelint-fix', 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/app/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | const sortOrderSmacss = require('stylelint-config-property-sort-order-smacss/generate'); 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-standard', 'stylelint-config-prettier'], 5 | plugins: ['stylelint-order'], 6 | rules: { 7 | 'declaration-no-important': true, 8 | 'order/properties-order': [sortOrderSmacss()], 9 | }, 10 | overrides: [ 11 | { 12 | files: ['**/*.ts', '**/*.tsx'], 13 | customSyntax: '@stylelint/postcss-css-in-js', 14 | rules: { 15 | // allow mui usage in css-in-js 16 | 'property-no-unknown': [true, { ignoreProperties: ['name'] }], 17 | 'value-keyword-case': ['lower', { ignoreProperties: ['name'] }], 18 | 'selector-class-pattern': [/Mui\w+/], 19 | }, 20 | }, 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/app/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /frontend/app/config/checkEnvConsistency.ts: -------------------------------------------------------------------------------- 1 | import { loadEnv } from 'vite'; 2 | 3 | const EXCLUDED_ENV_KEYS = ['VITE_USER_NODE_ENV']; 4 | 5 | const checkEnvConsistency = (mode: string): void => { 6 | const env = loadEnv(mode, process.cwd()); 7 | const exampleEnv = loadEnv('example', process.cwd()); 8 | 9 | const envFilename = mode === 'development' ? '.env' : '.env.' + mode; 10 | Object.keys(exampleEnv) 11 | .filter(key => !EXCLUDED_ENV_KEYS.includes(key)) 12 | .forEach(key => { 13 | if (!(key in env)) { 14 | throw new Error(`${key} is not defined in ${envFilename}`); 15 | } 16 | }); 17 | Object.keys(env) 18 | .filter(key => !EXCLUDED_ENV_KEYS.includes(key)) 19 | .forEach(key => { 20 | if (!(key in exampleEnv)) { 21 | throw new Error(`${key} is not defined in .env.example`); 22 | } 23 | }); 24 | }; 25 | 26 | export default checkEnvConsistency; 27 | -------------------------------------------------------------------------------- /frontend/app/config/index.ts: -------------------------------------------------------------------------------- 1 | export { default as checkEnvConsistency } from './checkEnvConsistency'; 2 | -------------------------------------------------------------------------------- /frontend/app/global.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers'; 4 | 5 | declare global { 6 | namespace jest { 7 | type Matchers = TestingLibraryMatchers< 8 | typeof expect.stringContaining, 9 | R 10 | >; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | Swarmion 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/frontend-app", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "scripts": { 7 | "analyze": "ANALYZE=true yarn build", 8 | "build": "vite build", 9 | "lint-fix": "yarn linter-base-config --fix", 10 | "lint-fix-all": "yarn lint-fix .", 11 | "linter-base-config": "eslint --ext=js,jsx,ts,tsx", 12 | "serve": "vite preview", 13 | "start": "vite --port 3000", 14 | "stylelint-base-config": "stylelint", 15 | "stylelint-fix": "yarn stylelint-base-config --fix", 16 | "test": "yarn test-linter && yarn test-type && yarn test-unit && yarn test-circular", 17 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js src", 18 | "test-linter": "yarn linter-base-config src", 19 | "test-stylelint": "yarn stylelint-base-config **/*.{ts,tsx}", 20 | "test-type": "tsc --noEmit", 21 | "test-unit": "vitest run --coverage --passWithNoTests" 22 | }, 23 | "dependencies": { 24 | "@emotion/react": "^11.9.3", 25 | "@emotion/styled": "^11.9.3", 26 | "@mui/icons-material": "^5.8.4", 27 | "@mui/lab": "^5.0.0-alpha.87", 28 | "@mui/material": "^5.8.5", 29 | "@mui/system": "^5.8.5", 30 | "@react-hookz/web": "^13.3.0", 31 | "@reduxjs/toolkit": "^1.8.2", 32 | "@swarmion-starter/frontend-shared": "1.0.0", 33 | "@swarmion-starter/users-contracts": "1.0.0", 34 | "@swarmion/serverless-contracts": "0.8.3", 35 | "axios": "0.27.2", 36 | "react": "^17.0.2", 37 | "react-dom": "^17.0.2", 38 | "react-intl": "^5.25.1", 39 | "react-redux": "^7.2.8", 40 | "react-router-dom": "^6.3.0", 41 | "redux": "^4.2.0" 42 | }, 43 | "devDependencies": { 44 | "@redux-devtools/extension": "^3.2.2", 45 | "@stylelint/postcss-css-in-js": "^0.37.3", 46 | "@testing-library/dom": "^8.14.0", 47 | "@testing-library/jest-dom": "^5.16.4", 48 | "@testing-library/react": "^12.1.5", 49 | "@testing-library/react-hooks": "^7.0.2", 50 | "@testing-library/user-event": "^13.5.0", 51 | "@types/dotenv-flow": "^3.2.0", 52 | "@types/node": "^17.0.45", 53 | "@types/react": "^17.0.47", 54 | "@types/react-dom": "^17.0.17", 55 | "@vitejs/plugin-react": "^2.1.0", 56 | "@vitest/coverage-c8": "^0.23.1", 57 | "dependency-cruiser": "^11.10.0", 58 | "dotenv-flow": "^3.2.0", 59 | "eslint": "^8.18.0", 60 | "eslint-plugin-jsx-a11y": "^6.5.1", 61 | "eslint-plugin-react": "^7.30.0", 62 | "eslint-plugin-react-hooks": "^4.6.0", 63 | "eslint-plugin-risxss": "^2.1.0", 64 | "husky": "^8.0.1", 65 | "postcss": "^8.4.14", 66 | "postcss-syntax": "^0.36.2", 67 | "prettier": "^2.7.1", 68 | "rollup-plugin-visualizer": "^5.8.1", 69 | "stylelint": "^14.9.1", 70 | "stylelint-config-prettier": "^9.0.3", 71 | "stylelint-config-property-sort-order-smacss": "^9.0.0", 72 | "stylelint-config-standard": "^25.0.0", 73 | "stylelint-order": "^5.0.0", 74 | "ts-node": "^10.8.1", 75 | "typescript": "^4.7.4", 76 | "vite": "^3.1.0", 77 | "vite-plugin-svgr": "^1.1.0", 78 | "vite-tsconfig-paths": "^3.5.0", 79 | "vitest": "^0.23.1" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /frontend/app/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "frontend/app", 3 | "projectType": "library", 4 | "tags": [], 5 | "implicitDependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /frontend/app/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { CssBaseline } from '@mui/material'; 2 | import { ThemeProvider } from '@mui/system'; 3 | import { IntlProvider } from 'react-intl'; 4 | import { Provider } from 'react-redux'; 5 | 6 | import { flattenMessages } from 'services/i18n'; 7 | import configureStore from 'store/configureStore'; 8 | import { muiTheme } from 'theme'; 9 | import { frFRMessages } from 'translations'; 10 | 11 | import AppRoutes from './AppRoutes'; 12 | 13 | const intlMessages = flattenMessages(frFRMessages); 14 | 15 | const App = (): JSX.Element => { 16 | const store = configureStore(); 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /frontend/app/src/AppRoutes.tsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from 'react'; 2 | import { BrowserRouter, Route, Routes } from 'react-router-dom'; 3 | 4 | import { NotFound } from '@swarmion-starter/frontend-shared'; 5 | 6 | import { RoutePaths } from 'types'; 7 | 8 | const Home = React.lazy(() => import('pages/Home/Home')); 9 | 10 | const AppRoutes = (): JSX.Element => ( 11 | }> 12 | 13 | 14 | } /> 15 | } /> 16 | 17 | 18 | 19 | ); 20 | 21 | export default AppRoutes; 22 | -------------------------------------------------------------------------------- /frontend/app/src/__fixtures__/state.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from 'store/configureStore'; 2 | 3 | import { user } from './user'; 4 | 5 | export const state: RootState = { 6 | user, 7 | }; 8 | 9 | export const emptyState: RootState = { 10 | user: null, 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/app/src/__fixtures__/user.ts: -------------------------------------------------------------------------------- 1 | import { UserEntity } from '@swarmion-starter/users-contracts'; 2 | 3 | export const user: UserEntity = { 4 | userId: '4dd02501-ef19-476c-8d9b-888e1e4dbe5f', 5 | userName: 'fooBar', 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/app/src/__mocks__/svgrMock.ts: -------------------------------------------------------------------------------- 1 | export default 'SvgrURL'; 2 | export const ReactComponent = 'span'; 3 | -------------------------------------------------------------------------------- /frontend/app/src/components/Title/Title.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@mui/material'; 2 | import { FormattedMessage } from 'react-intl'; 3 | 4 | const Title = (): JSX.Element => ( 5 | 6 | 7 | 8 | ); 9 | 10 | export default Title; 11 | -------------------------------------------------------------------------------- /frontend/app/src/components/Title/__tests__/Title.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from 'testUtils/react'; 2 | 3 | import Title from '../Title'; 4 | 5 | describe('Title component', () => { 6 | it("should display the heading 'Page not found'", () => { 7 | render(); 8 | 9 | const title = screen.getByRole('heading', { 10 | name: /Bienvenue sur le frontend de Swarmion/, 11 | }); 12 | expect(title).toBeInTheDocument(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/app/src/components/Title/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Title } from './Title'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Title'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="vite/client" /> 2 | 3 | interface ImportMetaEnv { 4 | readonly VITE_API_URL: string; 5 | readonly VITE_FRONT_URL: string; 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/app/src/favicon.svg: -------------------------------------------------------------------------------- 1 | <svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/> 3 | <path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/> 4 | <defs> 5 | <linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse"> 6 | <stop stop-color="#41D1FF"/> 7 | <stop offset="1" stop-color="#BD34FE"/> 8 | </linearGradient> 9 | <linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse"> 10 | <stop stop-color="#FFEA83"/> 11 | <stop offset="0.0833333" stop-color="#FFDD35"/> 12 | <stop offset="1" stop-color="#FFA800"/> 13 | </linearGradient> 14 | </defs> 15 | </svg> 16 | -------------------------------------------------------------------------------- /frontend/app/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | <StrictMode> 8 | <App /> 9 | </StrictMode>, 10 | document.getElementById('root'), 11 | ); 12 | -------------------------------------------------------------------------------- /frontend/app/src/pages/Home/Home.style.ts: -------------------------------------------------------------------------------- 1 | import { Button } from '@mui/material'; 2 | import { css } from '@mui/system'; 3 | 4 | import { styled } from 'theme'; 5 | 6 | const StyledButtonWithTheme = styled(Button)( 7 | ({ theme }) => css` 8 | background: ${theme.palette.secondary.main}; 9 | `, 10 | ); 11 | 12 | const StyledButton = styled(Button)` 13 | background: blueviolet; 14 | `; 15 | 16 | export { StyledButton, StyledButtonWithTheme }; 17 | -------------------------------------------------------------------------------- /frontend/app/src/pages/Home/Home.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@mui/material'; 2 | import { Box } from '@mui/system'; 3 | import { useAsync } from '@react-hookz/web/esnext'; 4 | import { nanoid } from '@reduxjs/toolkit'; 5 | import { getAxiosRequest } from '@swarmion/serverless-contracts'; 6 | import { useEffect } from 'react'; 7 | import { FormattedMessage } from 'react-intl'; 8 | import { useDispatch, useSelector } from 'react-redux'; 9 | 10 | import { getUserContract } from '@swarmion-starter/users-contracts'; 11 | 12 | import { Title } from 'components'; 13 | import client from 'services/networking/client'; 14 | import { getUser, setUser } from 'store/user'; 15 | 16 | import { StyledButton, StyledButtonWithTheme } from './Home.style'; 17 | 18 | const Home = (): JSX.Element => { 19 | const dispatch = useDispatch(); 20 | const userFromRedux = useSelector(getUser); 21 | const [{ result, error }, { execute }] = useAsync(() => 22 | getAxiosRequest(getUserContract, client, { 23 | pathParameters: { userId: nanoid() }, 24 | }), 25 | ); 26 | 27 | useEffect(() => { 28 | if (result === undefined) { 29 | return; 30 | } 31 | dispatch(setUser(result.data)); 32 | }, [dispatch, result]); 33 | 34 | if (error) { 35 | return ( 36 | <Box 37 | display="flex" 38 | flexDirection="column" 39 | justifyContent="center" 40 | alignContent="center" 41 | textAlign="center" 42 | height="100vh" 43 | maxWidth="100%" 44 | > 45 | Error: {error.message} 46 | </Box> 47 | ); 48 | } 49 | 50 | return ( 51 | <Box 52 | display="flex" 53 | flexDirection="column" 54 | justifyContent="center" 55 | alignContent="center" 56 | textAlign="center" 57 | height="100vh" 58 | maxWidth="100%" 59 | > 60 | <Title /> 61 | <Box marginTop={6}> 62 | <StyledButton variant="contained" onClick={() => void execute()}> 63 | <FormattedMessage id="home.button" /> 64 | </StyledButton> 65 | <StyledButtonWithTheme 66 | variant="contained" 67 | onClick={() => void execute()} 68 | > 69 | <FormattedMessage id="home.button" /> 70 | </StyledButtonWithTheme> 71 | </Box> 72 | <Typography variant="h5">User from api call</Typography> 73 | <Box marginTop={6}>{JSON.stringify(result?.data)}</Box> 74 | <Typography variant="h5">User from redux</Typography> 75 | <Box marginTop={6}>{JSON.stringify(userFromRedux)}</Box> 76 | </Box> 77 | ); 78 | }; 79 | 80 | export default Home; 81 | -------------------------------------------------------------------------------- /frontend/app/src/pages/Home/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/pages/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Home'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/services/i18n/flattenMessages.ts: -------------------------------------------------------------------------------- 1 | type Message = string | NestedDictionary; 2 | interface NestedDictionary { 3 | [x: string]: Message; 4 | } 5 | interface FlattenedDictionary { 6 | [x: string]: string; 7 | } 8 | 9 | export const flattenMessages = ( 10 | nestedMessages: NestedDictionary, 11 | prefix = '', 12 | ): FlattenedDictionary => 13 | Object.entries(nestedMessages).reduce( 14 | (messages: FlattenedDictionary, [key, value]) => { 15 | const prefixedKey = prefix !== '' ? `${prefix}.${key}` : key; 16 | 17 | if (typeof value === 'string') { 18 | messages[prefixedKey] = value; 19 | } else { 20 | Object.assign(messages, flattenMessages(value, prefixedKey)); 21 | } 22 | 23 | return messages; 24 | }, 25 | {}, 26 | ); 27 | 28 | export default flattenMessages; 29 | -------------------------------------------------------------------------------- /frontend/app/src/services/i18n/index.ts: -------------------------------------------------------------------------------- 1 | export { default as flattenMessages } from './flattenMessages'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/services/networking/client.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const baseApiUrl = import.meta.env.VITE_API_URL; 4 | 5 | const client = axios.create({ baseURL: baseApiUrl }); 6 | 7 | export default client; 8 | -------------------------------------------------------------------------------- /frontend/app/src/store/configureStore.ts: -------------------------------------------------------------------------------- 1 | import { composeWithDevTools } from '@redux-devtools/extension'; 2 | import { applyMiddleware, createStore, Store } from 'redux'; 3 | 4 | import reducer, { rootInitialState } from './reducers'; 5 | 6 | export type RootState = ReturnType<typeof reducer>; 7 | 8 | type AppStore = Store<RootState>; 9 | 10 | const configureStore = (preloadedState = rootInitialState): AppStore => { 11 | if (process.env.NODE_ENV === 'production') { 12 | return createStore(reducer, preloadedState, applyMiddleware()); 13 | } 14 | 15 | return createStore( 16 | reducer, 17 | preloadedState, 18 | composeWithDevTools(applyMiddleware()), 19 | ); 20 | }; 21 | 22 | export default configureStore; 23 | -------------------------------------------------------------------------------- /frontend/app/src/store/reducers.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import user, { initialState as userInitialState } from './user/slice'; 4 | 5 | export const rootInitialState = { 6 | user: userInitialState, 7 | }; 8 | 9 | const rootReducers = combineReducers({ 10 | user, 11 | }); 12 | 13 | export default rootReducers; 14 | -------------------------------------------------------------------------------- /frontend/app/src/store/user/__tests__/reducer.test.ts: -------------------------------------------------------------------------------- 1 | import { user } from '__fixtures__/user'; 2 | 3 | import reducer, { initialState, setUser } from '../slice'; 4 | 5 | /** 6 | * user reducers test 7 | */ 8 | describe('user reducers', () => { 9 | describe('setUser', () => { 10 | it('should return updated user with null initial state', () => { 11 | const action = setUser(user); 12 | const expectedState = user; 13 | 14 | expect(reducer(null, action)).toEqual(expectedState); 15 | }); 16 | 17 | it('should return updated user', () => { 18 | const action = setUser(user); 19 | const expectedState = user; 20 | 21 | expect(reducer(initialState, action)).toEqual(expectedState); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /frontend/app/src/store/user/__tests__/selector.test.ts: -------------------------------------------------------------------------------- 1 | import { state } from '__fixtures__/state'; 2 | 3 | import { getUser } from '../selectors'; 4 | 5 | /** 6 | * user selectors test 7 | */ 8 | describe('user slice selectors', () => { 9 | describe('getUser function', () => { 10 | it('should return the value stored in store.user', () => { 11 | expect(getUser(state)).toEqual(state.user); 12 | }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/app/src/store/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './selectors'; 2 | export { default as reducer } from './slice'; 3 | export * from './slice'; 4 | -------------------------------------------------------------------------------- /frontend/app/src/store/user/selectors.ts: -------------------------------------------------------------------------------- 1 | import { RootState } from 'store/configureStore'; 2 | 3 | import { UserData } from './slice'; 4 | 5 | export const getUser = (state: RootState): UserData => { 6 | return state.user; 7 | }; 8 | -------------------------------------------------------------------------------- /frontend/app/src/store/user/slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | 3 | import { UserEntity } from '@swarmion-starter/users-contracts'; 4 | 5 | export type UserData = UserEntity | null; 6 | 7 | export type UserState = Readonly<UserData>; 8 | 9 | export const initialState = null as UserState; 10 | 11 | const slice = createSlice({ 12 | name: 'user', 13 | initialState, 14 | reducers: { 15 | setUser: (_, action: PayloadAction<UserEntity>) => action.payload, 16 | }, 17 | }); 18 | 19 | export const { setUser } = slice.actions; 20 | export default slice.reducer; 21 | -------------------------------------------------------------------------------- /frontend/app/src/testUtils/WrapperForLocationTesting/WrapperForLocationTesting.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | 4 | export const LOCATION_HTML_ELEMENT_TEST_ID = 'redux-store'; 5 | 6 | type WrapperForLocationTestingProps = { 7 | children: ReactNode; 8 | }; 9 | 10 | const WrapperForLocationTesting = ({ 11 | children, 12 | }: WrapperForLocationTestingProps): JSX.Element => { 13 | const location = useLocation(); 14 | 15 | return ( 16 | <> 17 | {children} 18 | <div data-testid={LOCATION_HTML_ELEMENT_TEST_ID}> 19 | {JSON.stringify(location)} 20 | </div> 21 | </> 22 | ); 23 | }; 24 | 25 | export default WrapperForLocationTesting; 26 | -------------------------------------------------------------------------------- /frontend/app/src/testUtils/WrapperForLocationTesting/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | default as WrapperForLocationTesting, 3 | LOCATION_HTML_ELEMENT_TEST_ID, 4 | } from './WrapperForLocationTesting'; 5 | -------------------------------------------------------------------------------- /frontend/app/src/testUtils/WrapperForReduxTesting/WrapperForReduxTesting.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { useSelector } from 'react-redux'; 3 | 4 | import { RootState } from 'store/configureStore'; 5 | 6 | export const REDUX_STORE_HTML_ELEMENT_TEST_ID = 'redux-store'; 7 | 8 | type ReduxSelector = (rootState: RootState) => unknown; 9 | type WrapperForReduxTestingProps = { 10 | selector: ReduxSelector; 11 | children: ReactNode; 12 | }; 13 | 14 | const WrapperForReduxTesting = ({ 15 | selector, 16 | children, 17 | }: WrapperForReduxTestingProps): JSX.Element => { 18 | const state = useSelector(selector); 19 | 20 | return ( 21 | <> 22 | {children} 23 | <div data-testid={REDUX_STORE_HTML_ELEMENT_TEST_ID}> 24 | {JSON.stringify(state)} 25 | </div> 26 | </> 27 | ); 28 | }; 29 | 30 | export default WrapperForReduxTesting; 31 | -------------------------------------------------------------------------------- /frontend/app/src/testUtils/WrapperForReduxTesting/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | default as WrapperForReduxTesting, 3 | REDUX_STORE_HTML_ELEMENT_TEST_ID, 4 | } from './WrapperForReduxTesting'; 5 | -------------------------------------------------------------------------------- /frontend/app/src/testUtils/react-hooks.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | RenderHookResult, 3 | renderHook as rtlRenderHook, 4 | RenderHookOptions as RtlRenderHookOptions, 5 | } from '@testing-library/react-hooks'; 6 | import { ComponentType } from 'react'; 7 | import { IntlProvider } from 'react-intl'; 8 | import { Provider } from 'react-redux'; 9 | import { MemoryRouter } from 'react-router-dom'; 10 | import { createStore, Store } from 'redux'; 11 | 12 | import { state } from '__fixtures__/state'; 13 | import { flattenMessages } from 'services/i18n'; 14 | import { RootState } from 'store/configureStore'; 15 | import rootReducers from 'store/reducers'; 16 | import { frFRMessages } from 'translations'; 17 | 18 | interface RenderHookOptions<P> extends RtlRenderHookOptions<P> { 19 | messages?: Record<string, string>; 20 | initialState?: RootState; 21 | store?: Store<RootState>; 22 | } 23 | 24 | const defaultMessages = flattenMessages(frFRMessages); 25 | 26 | const renderHook = <P, R>( 27 | callback: (props: P) => R, 28 | { 29 | initialState = state, 30 | store = createStore(rootReducers, initialState), 31 | messages = defaultMessages, 32 | ...renderOptions 33 | }: RenderHookOptions<P> = {}, 34 | ): RenderHookResult<P, R> => { 35 | const Wrapper: ComponentType = ({ children }) => ( 36 | <Provider store={store}> 37 | <IntlProvider messages={messages} locale="fr" timeZone="Europe/Paris"> 38 | <MemoryRouter>{children}</MemoryRouter> 39 | </IntlProvider> 40 | </Provider> 41 | ); 42 | 43 | return rtlRenderHook(callback, { wrapper: Wrapper, ...renderOptions }); 44 | }; 45 | 46 | export * from '@testing-library/react-hooks'; 47 | export { renderHook }; 48 | -------------------------------------------------------------------------------- /frontend/app/src/testUtils/react.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from '@mui/system'; 2 | import { 3 | RenderResult, 4 | render as rtlRender, 5 | RenderOptions as RtlRenderOptions, 6 | } from '@testing-library/react'; 7 | import { IntlProvider } from 'react-intl'; 8 | import { Provider } from 'react-redux'; 9 | import { MemoryRouter, MemoryRouterProps } from 'react-router-dom'; 10 | import { createStore, Store } from 'redux'; 11 | 12 | import { state } from '__fixtures__/state'; 13 | import { flattenMessages } from 'services/i18n'; 14 | import { RootState } from 'store/configureStore'; 15 | import rootReducers from 'store/reducers'; 16 | import { muiTheme } from 'theme'; 17 | import { frFRMessages } from 'translations'; 18 | 19 | interface RenderOptions extends RtlRenderOptions { 20 | messages?: Record<string, string>; 21 | initialState?: RootState; 22 | store?: Store<RootState>; 23 | initialEntries?: MemoryRouterProps['initialEntries']; 24 | } 25 | 26 | const defaultMessages = flattenMessages(frFRMessages); 27 | 28 | /** 29 | * Render the component passed and wrap it in a redux provider, making it possible to test redux functionalities 30 | */ 31 | const render = ( 32 | ui: React.ReactElement, 33 | { 34 | initialState = state, 35 | store = createStore(rootReducers, initialState), 36 | messages = defaultMessages, 37 | initialEntries = ['/'], 38 | ...renderOptions 39 | }: RenderOptions = {}, 40 | ): RenderResult => { 41 | const Wrapper: React.ComponentType = ({ children }) => ( 42 | <ThemeProvider theme={muiTheme}> 43 | <Provider store={store}> 44 | <IntlProvider messages={messages} locale="fr" timeZone="Europe/Paris"> 45 | <MemoryRouter initialEntries={initialEntries}> 46 | {children} 47 | </MemoryRouter> 48 | </IntlProvider> 49 | </Provider> 50 | </ThemeProvider> 51 | ); 52 | 53 | return rtlRender(ui, { wrapper: Wrapper, ...renderOptions }); 54 | }; 55 | 56 | export * from '@testing-library/react'; 57 | export { render }; 58 | -------------------------------------------------------------------------------- /frontend/app/src/theme/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './spacing'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/theme/helpers/spacing.ts: -------------------------------------------------------------------------------- 1 | export const UNIT_TYPE = 'px'; 2 | export const UNIT = 8; 3 | 4 | export const getSpacing = (factor: number): string => { 5 | return `${factor * UNIT}${UNIT_TYPE}`; 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/app/src/theme/index.ts: -------------------------------------------------------------------------------- 1 | export { default as muiTheme } from './muiTheme'; 2 | export { default as styled } from './styled'; 3 | -------------------------------------------------------------------------------- /frontend/app/src/theme/muiTheme.ts: -------------------------------------------------------------------------------- 1 | import '@mui/lab/themeAugmentation'; 2 | import { createTheme, ThemeOptions } from '@mui/material/styles'; 3 | 4 | import { UNIT } from './helpers'; 5 | import { MuiButtonOverrides } from './overrides'; 6 | import { MuiTypographyVariants } from './variants'; 7 | 8 | export const muiThemeObject: ThemeOptions = { 9 | spacing: UNIT, 10 | palette: { 11 | text: { 12 | primary: '#2E2E2E', 13 | }, 14 | }, 15 | components: { 16 | MuiButton: { 17 | styleOverrides: MuiButtonOverrides, 18 | }, 19 | MuiTypography: { 20 | variants: MuiTypographyVariants, 21 | }, 22 | }, 23 | }; 24 | 25 | const muiTheme = createTheme(muiThemeObject); 26 | 27 | export default muiTheme; 28 | -------------------------------------------------------------------------------- /frontend/app/src/theme/overrides/MuiButton/MuiButtonOverrides.ts: -------------------------------------------------------------------------------- 1 | import { ComponentsOverrides } from '@mui/material'; 2 | 3 | const MuiButtonOverrides: ComponentsOverrides['MuiButton'] = { 4 | root: { 5 | textTransform: 'none', 6 | fontSize: '20px', 7 | boxShadow: 'none', 8 | '&:hover': { 9 | boxShadow: 'none', 10 | }, 11 | }, 12 | }; 13 | 14 | export default MuiButtonOverrides; 15 | -------------------------------------------------------------------------------- /frontend/app/src/theme/overrides/MuiButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MuiButtonOverrides } from './MuiButtonOverrides'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/theme/overrides/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MuiButton'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/theme/styled.ts: -------------------------------------------------------------------------------- 1 | import { createStyled } from '@mui/system'; 2 | 3 | import muiTheme from './muiTheme'; 4 | 5 | const styled = createStyled({ defaultTheme: muiTheme }); 6 | 7 | export default styled; 8 | -------------------------------------------------------------------------------- /frontend/app/src/theme/variants/MuiTypography/getH1Variants.ts: -------------------------------------------------------------------------------- 1 | import { ComponentsVariants } from '@mui/material'; 2 | 3 | const getH1Variants = (): NonNullable<ComponentsVariants['MuiTypography']> => { 4 | return [ 5 | { 6 | props: { variant: 'h1' }, 7 | style: { 8 | fontWeight: 700, 9 | fontSize: '28px', 10 | }, 11 | }, 12 | ]; 13 | }; 14 | 15 | export default getH1Variants; 16 | -------------------------------------------------------------------------------- /frontend/app/src/theme/variants/MuiTypography/getH2Variants.ts: -------------------------------------------------------------------------------- 1 | import { ComponentsVariants } from '@mui/material'; 2 | 3 | const getH2Variants = (): NonNullable<ComponentsVariants['MuiTypography']> => { 4 | return [ 5 | { 6 | props: { variant: 'h2' }, 7 | style: { 8 | fontWeight: 700, 9 | fontSize: '20px', 10 | }, 11 | }, 12 | ]; 13 | }; 14 | 15 | export default getH2Variants; 16 | -------------------------------------------------------------------------------- /frontend/app/src/theme/variants/MuiTypography/index.ts: -------------------------------------------------------------------------------- 1 | import getH1Variants from './getH1Variants'; 2 | import getH2Variants from './getH2Variants'; 3 | 4 | export const MuiTypographyVariants = [...getH1Variants(), ...getH2Variants()]; 5 | 6 | export { default as getH1Variants } from './getH1Variants'; 7 | export { default as getH2Variants } from './getH2Variants'; 8 | -------------------------------------------------------------------------------- /frontend/app/src/theme/variants/index.ts: -------------------------------------------------------------------------------- 1 | export * from './MuiTypography'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/translations/fr-FR.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | title: 'Bienvenue sur le frontend de Swarmion', 4 | button: 'Refaire le call API', 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/app/src/translations/index.ts: -------------------------------------------------------------------------------- 1 | export { default as frFRMessages } from './fr-FR'; 2 | -------------------------------------------------------------------------------- /frontend/app/src/types/RoutePaths.ts: -------------------------------------------------------------------------------- 1 | // RoutePaths is defined here and not in AppRoutes.tsx to prevent circular dependencies 2 | export enum RoutePaths { 3 | HOME_PAGE = '/', 4 | } 5 | -------------------------------------------------------------------------------- /frontend/app/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export { RoutePaths } from './RoutePaths'; 2 | -------------------------------------------------------------------------------- /frontend/app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 7 | "baseUrl": "src", 8 | "allowJs": false, 9 | "esModuleInterop": false, 10 | "allowSyntheticDefaultImports": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | "types": ["vite-plugin-svgr/client", "vitest/globals"] 18 | }, 19 | "references": [ 20 | { "path": "../shared/tsconfig.build.json" }, 21 | { "path": "../../contracts/users-contracts/tsconfig.build.json" } 22 | ], 23 | "include": ["./**/*.ts", "./**/*.tsx"] 24 | } 25 | -------------------------------------------------------------------------------- /frontend/app/vite.config.ts: -------------------------------------------------------------------------------- 1 | /// <reference types="vitest" /> 2 | 3 | import react from '@vitejs/plugin-react'; 4 | import { visualizer } from 'rollup-plugin-visualizer'; 5 | import { defineConfig } from 'vite'; 6 | import svgrPlugin from 'vite-plugin-svgr'; 7 | import tsconfigPaths from 'vite-tsconfig-paths'; 8 | 9 | import { checkEnvConsistency } from './config'; 10 | 11 | const plugins = [react(), tsconfigPaths(), svgrPlugin()]; 12 | 13 | if (process.env.ANALYZE === 'true') { 14 | plugins.push( 15 | visualizer({ 16 | open: true, 17 | filename: `bundles/${new Date().toISOString()}.html`, 18 | }), 19 | ); 20 | } 21 | 22 | export default defineConfig(({ mode }) => { 23 | checkEnvConsistency(mode); 24 | 25 | return { 26 | plugins, 27 | resolve: { 28 | alias: { 29 | 'react/jsx-runtime': 'react/jsx-runtime.js', 30 | }, 31 | }, 32 | test: { 33 | globals: true, 34 | environment: 'jsdom', 35 | setupFiles: './vitest.setup.ts', 36 | }, 37 | }; 38 | }); 39 | -------------------------------------------------------------------------------- /frontend/app/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | -------------------------------------------------------------------------------- /frontend/cloudfront/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig({}); 4 | -------------------------------------------------------------------------------- /frontend/cloudfront/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/cloudfront/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /frontend/cloudfront/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /frontend/cloudfront/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # load params into variables, ie: running "./deploy.sh --stage staging" will create the `$stage` variable with the value "staging" 4 | while [ $# -gt 0 ]; do 5 | if [[ $1 == *"--"* ]]; then 6 | v="${1/--/}" 7 | declare "$v"="$2" 8 | fi 9 | shift 10 | done 11 | 12 | stages=("staging production") 13 | 14 | if [[ -n "${stage}" ]] && [[ ! "${stages[*]}" =~ "${stage}" ]]; then 15 | echo "Stage should be either empty or \`staging\` or \`production\`" 16 | exit 1 17 | fi 18 | 19 | cd ../app || exit 1 20 | yarn build --mode "$stage" 21 | cd ../cloudfront || exit 1 22 | if [[ -z "${stage}" ]]; then 23 | yarn serverless deploy 24 | else 25 | yarn serverless deploy --stage "$stage" 26 | fi 27 | -------------------------------------------------------------------------------- /frontend/cloudfront/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/frontend-cloudfront", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "scripts": { 7 | "deploy": "./deploy.sh", 8 | "lint-fix": "yarn linter-base-config --fix", 9 | "lint-fix-all": "yarn lint-fix .", 10 | "linter-base-config": "eslint --ext=js,ts", 11 | "remove": "serverless remove", 12 | "sls-info": "serverless info --verbose", 13 | "test": "yarn test-linter && yarn test-type && yarn test-circular", 14 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js .", 15 | "test-linter": "yarn linter-base-config .", 16 | "test-type": "tsc --noEmit" 17 | }, 18 | "dependencies": { 19 | "@swarmion-starter/serverless-configuration": "1.0.0", 20 | "@swarmion-starter/users-contracts": "1.0.0" 21 | }, 22 | "devDependencies": { 23 | "@serverless/typescript": "^3.19.0", 24 | "@types/node": "^17.0.45", 25 | "@vitest/coverage-c8": "^0.23.1", 26 | "dependency-cruiser": "^11.10.0", 27 | "eslint": "^8.18.0", 28 | "serverless": "^3.19.0", 29 | "serverless-lift": "^1.19.0", 30 | "ts-node": "^10.8.1", 31 | "typescript": "^4.7.4", 32 | "vitest": "^0.23.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /frontend/cloudfront/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "frontend/cloudfront", 3 | "projectType": "application", 4 | "tags": [], 5 | "implicitDependencies": ["frontend-app"] 6 | } 7 | -------------------------------------------------------------------------------- /frontend/cloudfront/serverless.ts: -------------------------------------------------------------------------------- 1 | import { AWS } from '@serverless/typescript'; 2 | import type { Lift } from 'serverless-lift'; 3 | 4 | import { 5 | frameworkVersion, 6 | projectName, 7 | sharedParams, 8 | sharedProviderConfig, 9 | } from '@swarmion-starter/serverless-configuration'; 10 | 11 | const serverlessConfiguration: AWS & Lift = { 12 | service: `${projectName}-frontend`, // Keep it short to have role name below 64 13 | frameworkVersion, 14 | plugins: ['serverless-lift'], 15 | provider: sharedProviderConfig, 16 | params: sharedParams, 17 | custom: { 18 | projectName, 19 | }, 20 | constructs: { 21 | app: { 22 | type: 'static-website', 23 | path: '../app/dist', 24 | }, 25 | }, 26 | lift: { automaticPermissions: false }, 27 | resources: { 28 | Description: 'Frontend cloudfront service', 29 | }, 30 | }; 31 | 32 | module.exports = serverlessConfiguration; 33 | -------------------------------------------------------------------------------- /frontend/cloudfront/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "preserveSymlinks": true, 5 | "baseUrl": ".", 6 | "esModuleInterop": true 7 | }, 8 | "references": [ 9 | { "path": "../../packages/serverless-configuration/tsconfig.build.json" } 10 | ], 11 | "include": ["./**/*.ts"], 12 | "ts-node": { 13 | "files": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/shared/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | const pathNot = ['dist']; 4 | 5 | module.exports = commonDependencyCruiserConfig({ pathNot }); 6 | -------------------------------------------------------------------------------- /frontend/shared/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /frontend/shared/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/shared/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /frontend/shared/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /frontend/shared/babel.config.js: -------------------------------------------------------------------------------- 1 | const commonBabelConfig = require('../../commonConfiguration/babel.config'); 2 | 3 | const presets = [ 4 | [ 5 | '@babel/preset-react', 6 | { 7 | runtime: 'automatic', 8 | }, 9 | ], 10 | ]; 11 | 12 | module.exports = commonBabelConfig([], presets); 13 | -------------------------------------------------------------------------------- /frontend/shared/global.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-types */ 2 | /* eslint-disable @typescript-eslint/no-unused-vars */ 3 | import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers'; 4 | 5 | declare global { 6 | namespace jest { 7 | type Matchers<R = void, T = {}> = TestingLibraryMatchers< 8 | typeof expect.stringContaining, 9 | R 10 | >; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/frontend-shared", 3 | "version": "1.0.0", 4 | "license": "UNLICENSED", 5 | "sideEffects": false, 6 | "files": [ 7 | "dist" 8 | ], 9 | "main": "dist/cjs/index.js", 10 | "module": "dist/esm/index.js", 11 | "types": "dist/types/index.d.ts", 12 | "scripts": { 13 | "lint-fix": "yarn linter-base-config --fix", 14 | "lint-fix-all": "yarn lint-fix .", 15 | "linter-base-config": "eslint", 16 | "package": "rm -rf dist && yarn package-cjs && yarn package-esm && yarn package-types", 17 | "package-cjs": "NODE_ENV=cjs yarn transpile --out-dir dist/cjs --source-maps", 18 | "package-esm": "NODE_ENV=esm yarn transpile --out-dir dist/esm --source-maps", 19 | "package-types": "ttsc -p tsconfig.build.json", 20 | "test": "yarn test-linter && yarn test-type && yarn test-unit && yarn test-circular", 21 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js src", 22 | "test-linter": "yarn linter-base-config .", 23 | "test-type": "tsc --noEmit --emitDeclarationOnly false", 24 | "test-unit": "vitest run --passWithNoTests --coverage", 25 | "transpile": "babel src --extensions .ts,.tsx --copy-files --quiet", 26 | "watch": "rm -rf dist && concurrently 'yarn:package-* --watch'" 27 | }, 28 | "devDependencies": { 29 | "@babel/cli": "^7.17.10", 30 | "@babel/core": "^7.18.5", 31 | "@babel/plugin-transform-runtime": "^7.18.5", 32 | "@babel/preset-env": "^7.18.2", 33 | "@babel/preset-react": "^7.17.12", 34 | "@babel/preset-typescript": "^7.17.12", 35 | "@emotion/react": "^11.9.3", 36 | "@emotion/styled": "^11.9.3", 37 | "@mui/icons-material": "^5.8.4", 38 | "@mui/lab": "^5.0.0-alpha.87", 39 | "@mui/material": "^5.8.5", 40 | "@mui/system": "^5.8.5", 41 | "@testing-library/dom": "^8.14.0", 42 | "@testing-library/jest-dom": "^5.16.4", 43 | "@testing-library/react": "^12.1.5", 44 | "@testing-library/react-hooks": "^7.0.2", 45 | "@testing-library/user-event": "^13.5.0", 46 | "@types/css-mediaquery": "^0.1.1", 47 | "@types/node": "^17.0.45", 48 | "@types/react": "^17.0.47", 49 | "@vitejs/plugin-react": "^2.1.0", 50 | "@vitest/coverage-c8": "^0.23.1", 51 | "@zerollup/ts-transform-paths": "^1.7.18", 52 | "babel-plugin-module-resolver": "^4.1.0", 53 | "concurrently": "^7.2.2", 54 | "css-mediaquery": "^0.1.2", 55 | "dependency-cruiser": "^11.10.0", 56 | "eslint": "^8.18.0", 57 | "json-schema-to-ts": "^2.5.3", 58 | "prettier": "^2.7.1", 59 | "react-intl": "^5.25.1", 60 | "react-router-dom": "^6.3.0", 61 | "ts-node": "^10.8.1", 62 | "ttypescript": "^1.5.13", 63 | "typescript": "^4.7.4", 64 | "vitest": "^0.23.1" 65 | }, 66 | "nx": { 67 | "targets": { 68 | "package": { 69 | "outputs": [ 70 | "frontend/shared/dist" 71 | ] 72 | } 73 | } 74 | }, 75 | "peerDependencies": { 76 | "react": "^17.0.2", 77 | "react-dom": "^17.0.2" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /frontend/shared/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "frontend/shared", 3 | "tags": [], 4 | "projectType": "library", 5 | "implicitDependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /frontend/shared/src/components/NotFound/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@mui/material'; 2 | 3 | const NotFound = (): JSX.Element => ( 4 | <Typography variant="h1">Page not found</Typography> 5 | ); 6 | 7 | export default NotFound; 8 | -------------------------------------------------------------------------------- /frontend/shared/src/components/NotFound/__tests__/NotFound.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | 3 | import NotFound from '../NotFound'; 4 | 5 | describe('NotFound component', () => { 6 | it("should display the heading 'Page not found'", () => { 7 | render(<NotFound />); 8 | 9 | const pageNotFound = screen.getByRole('heading', { 10 | name: /Page not found/, 11 | }); 12 | expect(pageNotFound).toBeInTheDocument(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/shared/src/components/NotFound/index.ts: -------------------------------------------------------------------------------- 1 | export { default as NotFound } from './NotFound'; 2 | -------------------------------------------------------------------------------- /frontend/shared/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './NotFound'; 2 | -------------------------------------------------------------------------------- /frontend/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components'; 2 | -------------------------------------------------------------------------------- /frontend/shared/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "exclude": ["./**/*.test.ts", "./**/*.test.tsx", "./vite*"] 7 | } 8 | -------------------------------------------------------------------------------- /frontend/shared/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "esModuleInterop": true, 7 | "composite": true, 8 | "plugins": [{ "transform": "@zerollup/ts-transform-paths" }], 9 | "emitDeclarationOnly": true, 10 | "jsx": "react-jsx", 11 | "outDir": "./dist/types" 12 | }, 13 | "exclude": ["./dist"], 14 | "include": ["./**/*.ts", "./**/*.tsx"] 15 | } 16 | -------------------------------------------------------------------------------- /frontend/shared/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | globals: true, 8 | environment: 'jsdom', 9 | setupFiles: './vitest.setup.ts', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/shared/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | -------------------------------------------------------------------------------- /get-affected-paths.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | IFS=$'\n\t' 4 | 5 | readonly PROJECT_TYPE=$1 6 | 7 | # exit if PROJECT_TYPE not in 'apps' or 'libs' 8 | if [[ "$PROJECT_TYPE" != "apps" && "$PROJECT_TYPE" != "libs" ]]; then 9 | echo "PROJECT_TYPE must be either 'apps' or 'libs'" 10 | exit 1 11 | fi 12 | 13 | set -u 14 | 15 | replace() { 16 | cat workspace.json | jq ".projects[\"$1\"]" 17 | } 18 | 19 | export -f replace 20 | 21 | readonly AFFECTED_STRING=$(yarn nx affected:"$PROJECT_TYPE" --plain) 22 | readonly AFFECTED_ARRAY=($(echo "$AFFECTED_STRING" | tr ' ' '\n')) 23 | 24 | RESULT='' 25 | 26 | for app in "${AFFECTED_ARRAY[@]}"; do 27 | if [[ -z "${RESULT}" ]]; then 28 | RESULT=$(replace "$app") 29 | else 30 | RESULT="$RESULT,$(replace "$app")" 31 | fi 32 | done 33 | 34 | RESULT="[$RESULT]" 35 | 36 | echo "$RESULT" 37 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@nrwl/workspace/presets/npm.json", 3 | "npmScope": "swarmion-starter", 4 | "tasksRunnerOptions": { 5 | "default": { 6 | "runner": "@nrwl/workspace/tasks-runners/default", 7 | "options": { 8 | "cacheableOperations": [ 9 | "build", 10 | "package", 11 | "test", 12 | "test-type", 13 | "test-linter", 14 | "test-stylelint", 15 | "test-circular", 16 | "test-unit" 17 | ], 18 | "cacheDirectory": "nx-cache" 19 | } 20 | } 21 | }, 22 | "targetDependencies": { 23 | "build": [ 24 | { 25 | "target": "package", 26 | "projects": "dependencies" 27 | } 28 | ], 29 | "deploy": [ 30 | { 31 | "target": "package", 32 | "projects": "dependencies" 33 | }, 34 | { 35 | "target": "build", 36 | "projects": "dependencies" 37 | }, 38 | { 39 | "target": "deploy", 40 | "projects": "dependencies" 41 | } 42 | ], 43 | "package": [ 44 | { 45 | "target": "package", 46 | "projects": "dependencies" 47 | } 48 | ], 49 | "test": [ 50 | { 51 | "target": "package", 52 | "projects": "dependencies" 53 | } 54 | ], 55 | "test-linter": [ 56 | { 57 | "target": "package", 58 | "projects": "dependencies" 59 | } 60 | ], 61 | "test-type": [ 62 | { 63 | "target": "package", 64 | "projects": "dependencies" 65 | } 66 | ], 67 | "test-unit": [ 68 | { 69 | "target": "package", 70 | "projects": "dependencies" 71 | } 72 | ] 73 | }, 74 | "affected": { 75 | "defaultBase": "main" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/root", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "workspaces": [ 7 | "backend/*", 8 | "contracts/*", 9 | "frontend/*", 10 | "packages/*" 11 | ], 12 | "scripts": { 13 | "build-all": "nx run-many --target=build --all --parallel=4", 14 | "check-audit": "check-audit --yarn", 15 | "deploy": "nx run-many --target=deploy --all --parallel=4", 16 | "deploy-affected": "nx affected --target=deploy", 17 | "generate-library": "nx generate @swarmion/nx-plugin:library", 18 | "generate-service": "nx generate @swarmion/nx-plugin:service", 19 | "graph": "nx dep-graph", 20 | "info": "nx run-many --target=sls-info --all --parallel=4", 21 | "lint-fix": "nx run-many --target=lint-fix --all --parallel=4", 22 | "linter-base-config": "eslint", 23 | "package": "nx run-many --target=package --all --parallel=4", 24 | "postinstall": "husky install && syncpack format", 25 | "resolve-audit": "resolve-audit --yarn", 26 | "test": "nx run-many --target=test --all --parallel=4", 27 | "test-affected": "nx affected --target=test", 28 | "test-circular": "nx run-many --target=test-circular --all --parallel=4", 29 | "test-linter": "nx run-many --target=test-linter --all --parallel=4", 30 | "test-type": "nx run-many --target=test-type --all --parallel=4", 31 | "test-unit": "nx run-many --target=test-unit --all --parallel=4" 32 | }, 33 | "devDependencies": { 34 | "@commitlint/cli": "^17.0.2", 35 | "@commitlint/config-conventional": "^17.0.2", 36 | "@nrwl/devkit": "^14.3.6", 37 | "@nrwl/linter": "^14.3.6", 38 | "@nrwl/tao": "^14.3.6", 39 | "@nrwl/workspace": "^14.3.6", 40 | "@swarmion/nx-plugin": "0.8.3", 41 | "@typescript-eslint/eslint-plugin": "^5.29.0", 42 | "@typescript-eslint/parser": "^5.29.0", 43 | "aws-sdk": "^2.1160.0", 44 | "dependency-cruiser": "^11.10.0", 45 | "eslint": "^8.18.0", 46 | "eslint-config-prettier": "^8.5.0", 47 | "eslint-import-resolver-typescript": "^2.7.1", 48 | "eslint-plugin-import": "^2.26.0", 49 | "eslint-plugin-prefer-arrow": "^1.2.3", 50 | "eslint-plugin-prettier": "^4.0.0", 51 | "fresko": "^0.5.3", 52 | "husky": "^8.0.1", 53 | "lint-staged": "^13.0.2", 54 | "lodash": "^4.17.21", 55 | "npm-audit-resolver": "^3.0.0-7", 56 | "nx": "^14.3.6", 57 | "prettier": "^2.7.1", 58 | "syncpack": "^7.2.2", 59 | "typescript": "^4.7.4" 60 | }, 61 | "engines": { 62 | "node": "^16.15.0" 63 | }, 64 | "packageManager": "yarn@3.2.1" 65 | } 66 | -------------------------------------------------------------------------------- /packages/serverless-configuration/.dependency-cruiser.js: -------------------------------------------------------------------------------- 1 | const commonDependencyCruiserConfig = require('../../commonConfiguration/dependency-cruiser.config'); 2 | 3 | module.exports = commonDependencyCruiserConfig({ path: './src' }); 4 | -------------------------------------------------------------------------------- /packages/serverless-configuration/.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/serverless-configuration/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | project: ['./tsconfig.json'], 4 | tsconfigRootDir: __dirname, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/serverless-configuration/.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('../../.lintstagedrc'); 2 | module.exports = baseConfig; 3 | -------------------------------------------------------------------------------- /packages/serverless-configuration/.vscode: -------------------------------------------------------------------------------- 1 | ../../commonConfiguration/.vscode -------------------------------------------------------------------------------- /packages/serverless-configuration/babel.config.js: -------------------------------------------------------------------------------- 1 | const commonBabelConfig = require('../../commonConfiguration/babel.config'); 2 | 3 | module.exports = commonBabelConfig(); 4 | -------------------------------------------------------------------------------- /packages/serverless-configuration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@swarmion-starter/serverless-configuration", 3 | "private": true, 4 | "version": "1.0.0", 5 | "license": "UNLICENSED", 6 | "sideEffects": false, 7 | "files": [ 8 | "dist" 9 | ], 10 | "main": "dist/cjs/index.js", 11 | "module": "dist/esm/index.js", 12 | "types": "dist/types/index.d.ts", 13 | "scripts": { 14 | "lint-fix": "yarn linter-base-config --fix", 15 | "lint-fix-all": "yarn lint-fix .", 16 | "linter-base-config": "eslint --ext=js,ts .", 17 | "package": "rm -rf dist && yarn package-cjs && yarn package-esm && yarn package-types", 18 | "package-cjs": "NODE_ENV=cjs yarn transpile --out-dir dist/cjs --source-maps", 19 | "package-esm": "NODE_ENV=esm yarn transpile --out-dir dist/esm --source-maps", 20 | "package-types": "ttsc -p tsconfig.build.json", 21 | "test": "yarn test-linter && yarn test-type && yarn test-circular", 22 | "test-circular": "yarn depcruise --validate .dependency-cruiser.js src", 23 | "test-linter": "yarn linter-base-config .", 24 | "test-type": "tsc --noEmit --emitDeclarationOnly false", 25 | "transpile": "babel src --extensions .ts --quiet", 26 | "watch": "rm -rf dist && concurrently 'yarn:package-* --watch'" 27 | }, 28 | "dependencies": { 29 | "@babel/runtime": "^7.18.3" 30 | }, 31 | "devDependencies": { 32 | "@babel/cli": "^7.17.10", 33 | "@babel/core": "^7.18.5", 34 | "@babel/plugin-transform-runtime": "^7.18.5", 35 | "@babel/preset-env": "^7.18.2", 36 | "@babel/preset-typescript": "^7.17.12", 37 | "@serverless/typescript": "^3.19.0", 38 | "@types/node": "^17.0.45", 39 | "@vitest/coverage-c8": "^0.23.1", 40 | "@zerollup/ts-transform-paths": "^1.7.18", 41 | "babel-plugin-module-resolver": "^4.1.0", 42 | "concurrently": "^7.2.2", 43 | "dependency-cruiser": "^11.10.0", 44 | "eslint": "^8.18.0", 45 | "prettier": "^2.7.1", 46 | "ts-node": "^10.8.1", 47 | "ttypescript": "^1.5.13", 48 | "typescript": "^4.7.4", 49 | "vitest": "^0.23.1" 50 | }, 51 | "nx": { 52 | "targets": { 53 | "package": { 54 | "outputs": [ 55 | "packages/serverless-configuration/dist" 56 | ] 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/serverless-configuration/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "packages/serverless-configuration", 3 | "projectType": "library", 4 | "tags": [], 5 | "implicitDependencies": [] 6 | } 7 | -------------------------------------------------------------------------------- /packages/serverless-configuration/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sharedConfig'; 2 | -------------------------------------------------------------------------------- /packages/serverless-configuration/src/sharedConfig.ts: -------------------------------------------------------------------------------- 1 | export const projectName = 'swarmion-starter'; 2 | export const region = 'eu-west-1'; 3 | export const frameworkVersion = '>=3.0.0'; 4 | 5 | export const defaultEnvironment = 'dev'; 6 | 7 | export const sharedProviderConfig = { 8 | name: 'aws', 9 | runtime: 'nodejs16.x', 10 | architecture: 'arm64', 11 | region, 12 | profile: '${param:profile}', // Used to point to the right AWS account 13 | stage: "${opt:stage, 'dev'}", // Doc: https://www.serverless.com/framework/docs/providers/aws/guide/credentials/ 14 | environment: { 15 | AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1', 16 | NODE_OPTIONS: '--enable-source-maps --stack-trace-limit=1000', 17 | }, 18 | } as const; 19 | 20 | /** 21 | * A set of shared parameters, scoped by stage. You can extend them to add other shared parameters between services. 22 | * 23 | * See https://www.serverless.com/framework/docs/providers/aws/guide/variables#referencing-parameters 24 | * 25 | * An empty string for a profile means that the default profile will be used 26 | */ 27 | export const sharedParams = { 28 | dev: { profile: 'swarmion-starter-developer' }, 29 | staging: { profile: '' }, 30 | production: { profile: '' }, 31 | }; 32 | 33 | export const sharedEsbuildConfig = { 34 | packager: 'yarn', 35 | bundle: true, 36 | minify: true, 37 | keepNames: true, 38 | sourcemap: true, 39 | exclude: ['aws-sdk'], 40 | target: 'node16', 41 | platform: 'node', 42 | /** 43 | * Sets the resolution order for esbuild. 44 | * 45 | * In order to enable tree-shaking of packages, we need specify `module` first (ESM) 46 | * Because it defaults to "main" first (CJS, not tree shakeable) 47 | * https://esbuild.github.io/api/#main-fields 48 | */ 49 | mainFields: ['module', 'main'], 50 | concurrency: 5, 51 | }; 52 | -------------------------------------------------------------------------------- /packages/serverless-configuration/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src" 5 | }, 6 | "exclude": ["./vite*", "./**/*.test.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/serverless-configuration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "composite": true, 6 | "plugins": [{ "transform": "@zerollup/ts-transform-paths" }], 7 | "emitDeclarationOnly": true, 8 | "outDir": "./dist/types" 9 | }, 10 | "exclude": ["./dist"], 11 | "include": ["./**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/serverless-configuration/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /swarmion-starter.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "settings": { 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "editor.formatOnSave": true, 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": true, 7 | "source.fixAll.stylelint": true 8 | }, 9 | "stylelint.validate": ["typescript", "typescriptreact"], 10 | "stylelint.customSyntax": "@stylelint/postcss-css-in-js", 11 | "search.exclude": { 12 | "**/coverage": true, 13 | "**/node_modules": true, 14 | "**/.serverless": true, 15 | "**/build": true, 16 | "**/bundles": true, 17 | "**/dist": true, 18 | ".yarn": true, 19 | "yarn.lock": true 20 | }, 21 | "[dotenv][ignore][properties][shellscript]": { 22 | "editor.defaultFormatter": "foxundermoon.shell-format" 23 | }, 24 | "[html][javascript][json]": { 25 | "editor.defaultFormatter": "esbenp.prettier-vscode" 26 | }, 27 | "typescript.tsdk": "node_modules/typescript/lib", 28 | "cSpell.words": ["esbuild", "swarmion"], 29 | "vitest.commandLine": "yarn vitest", 30 | "vitest.disabledWorkspaceFolders": ["swarmion-starter root"] 31 | }, 32 | "extensions": { 33 | "recommendations": [ 34 | "esbenp.prettier-vscode", 35 | "dbaeumer.vscode-eslint", 36 | "editorconfig.editorconfig", 37 | "foxundermoon.shell-format", 38 | "streetsidesoftware.code-spell-checker", 39 | "streetsidesoftware.code-spell-checker-french", 40 | "stylelint.vscode-stylelint", 41 | "styled-components.vscode-styled-components", 42 | "SebastianBille.iam-legend", 43 | "ZixuanChen.vitest-explorer" 44 | ] 45 | }, 46 | "folders": [ 47 | { 48 | "path": ".", 49 | "name": "swarmion-starter root" 50 | }, 51 | { 52 | "path": "backend/core", 53 | "name": "backend core [service]" 54 | }, 55 | { 56 | "path": "backend/forum", 57 | "name": "backend forum [service]" 58 | }, 59 | { 60 | "path": "backend/users", 61 | "name": "backend users [service]" 62 | }, 63 | { 64 | "path": "packages/serverless-configuration", 65 | "name": "serverless configuration [library]" 66 | }, 67 | { 68 | "path": "contracts/core-contracts", 69 | "name": "contracts core [library]" 70 | }, 71 | { 72 | "path": "contracts/forum-contracts", 73 | "name": "contracts forum [library]" 74 | }, 75 | { 76 | "path": "contracts/users-contracts", 77 | "name": "contracts users [library]" 78 | }, 79 | { 80 | "path": "frontend/app", 81 | "name": "frontend app [site]" 82 | }, 83 | { 84 | "path": "frontend/shared", 85 | "name": "frontend shared [library]" 86 | }, 87 | { 88 | "path": "frontend/cloudfront", 89 | "name": "frontend cloudfront [service]" 90 | } 91 | ] 92 | } 93 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "esModuleInterop": true, 5 | "types": ["node", "vitest/globals"], 6 | "lib": ["es2020"], 7 | "removeComments": true, 8 | "moduleResolution": "node", 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "sourceMap": true, 12 | "strict": true, 13 | "noImplicitReturns": true, 14 | "skipLibCheck": true, 15 | "declaration": true, 16 | "declarationMap": true 17 | }, 18 | "ts-node": { 19 | "require": ["tsconfig-paths/register"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "projects": { 4 | "backend-core": "backend/core", 5 | "backend-forum": "backend/forum", 6 | "backend-users": "backend/users", 7 | "core-contracts": "contracts/core-contracts", 8 | "forum-contracts": "contracts/forum-contracts", 9 | "frontend-app": "frontend/app", 10 | "frontend-cloudfront": "frontend/cloudfront", 11 | "frontend-shared": "frontend/shared", 12 | "serverless-configuration": "packages/serverless-configuration", 13 | "users-contracts": "contracts/users-contracts" 14 | } 15 | } 16 | --------------------------------------------------------------------------------