├── .babelrc ├── .editorconfig ├── .env ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ ├── default.md │ └── version_release.md ├── _workflow-samples │ ├── README.md │ ├── deploy-gh.yml │ └── deploy-s3.yml └── workflows │ └── checks.yml ├── .gitignore ├── .nvmrc ├── .prettierrc ├── .stylelintrc.json ├── LICENSE ├── README.md ├── _README.md ├── app ├── main.tsx ├── media │ └── layout │ │ └── ds-logo-pos.svg ├── styles │ ├── color-palette.ts │ └── theme.ts └── vite-env.d.ts ├── eslint.config.mjs ├── index.html ├── jest.config.js ├── jest.setup.ts ├── package.json ├── pnpm-lock.yaml ├── public └── meta │ ├── android-chrome.png │ ├── apple-touch-icon.png │ ├── favicon.png │ └── meta-image.png ├── test └── example.test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── vite-plugin-port-scanner.ts └── vite.config.mts /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": ["@babel/preset-react", "@babel/preset-typescript"], 5 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 6 | } 7 | }, 8 | "plugins": ["babel-plugin-styled-components"] 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.{js}] 14 | charset = utf-8 15 | 16 | # Indentation override for all JS under lib directory 17 | [lib/**.js] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | # Matches the exact files either package.json or .travis.yml 22 | [{package.json,.travis.yml}] 23 | indent_style = space 24 | indent_size = 2 25 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | VITE_APP_TITLE=Project Seed 2 | VITE_APP_DESCRIPTION=Starter application by Development Seed 3 | 4 | # If the app is being served in from a subfolder, the domain url must be set. 5 | # For example, if the app is served from /mysite: 6 | # VITE_BASE_URL=http://example.com/mysite -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Why are you creating this Pull Request? 4 | 5 | - [Version Release](?title=Deploy%20vX.X.X&expand=1&template=version_release.md) 6 | - [Other](?expand=1&template=default.md) -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/default.md: -------------------------------------------------------------------------------- 1 | ## What am I changing and why 2 | 3 | _Replace with brief description of the update and why it is needed. Link to relevant issues._ 4 | 5 | ## How to test 6 | _Replace with instructions on how this change be tested/verified._ 7 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/version_release.md: -------------------------------------------------------------------------------- 1 | _The PR name should be the version to be deployed (ex: v1.0.1)_ 2 | 3 | - [ ] Bump the version in the package.json or equivalent. 4 | 5 | --- 6 | 7 | # Changelog vX.X.X 8 | 9 | ## 🎉 Features 10 | - 11 | 12 | ## 🚀 Improvements 13 | - 14 | 15 | ## 🐛 Fixes 16 | - 17 | -------------------------------------------------------------------------------- /.github/_workflow-samples/README.md: -------------------------------------------------------------------------------- 1 | # `deploy-s3-yml` 2 | A workflow that builds the site and deploys it to S3. 3 | 4 | This workflow gets triggered with every push to the main branch, and doesn't verify if the checks were successful. It relies on branch protection to do so. 5 | 6 | ## First-time setup 7 | - create a bucket on S3 and enable 'Static website hosting' with both the Index and Error document set to `index.html`. To do this programmatically: 8 | ``` 9 | aws s3 mb [BUCKET NAME] 10 | aws s3 website [BUCKET NAME] --index-document index.html --error-document index.html 11 | aws s3api put-bucket-tagging --bucket [BUCKET NAME] --tagging 'TagSet=[{Key=Project,Value=[PROJECT TAG]}]' 12 | ``` 13 | - create an IAM with a policy that provides it with programmatic access to the bucket 14 | - add the AWS Access Key and Secret from the IAM [as encrypted secrets to the project repository](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository). Use `AWS_ACCESS_KEY_ID` & `AWS_SECRET_ACCESS_KEY` 15 | - add the bucket name as an environment variable (`DEPLOY_BUCKET`) to the deploy workflow. Omit `s3://` from the bucket name. 16 | 17 | ## Serving site from sub-path 18 | This workflow assumes that the site is served from the root of the URL (eg. devseed.com). To support a URL served from a sub-path (eg. devseed.com/explorer), add the following step: 19 | 20 | ``` 21 | - name: Serve site from subpath 22 | run: | 23 | cd dist 24 | mkdir 25 | mv assets /assets 26 | cp index.html 27 | ``` 28 | 29 | # `deploy-gh-yml` 30 | A workflow that builds the site and deploys it to Github pages. 31 | 32 | This workflow gets triggered with every push to the main branch, and doesn't verify if the checks were successful. It relies on branch protection to do so. 33 | 34 | # S3 previews 35 | Check the [Implementing S3 deploy previews](https://github.com/developmentseed/how/issues/423) guide to set up S3 previews for feature branches. 36 | -------------------------------------------------------------------------------- /.github/_workflow-samples/deploy-gh.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Github Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | env: 9 | VITE_BASE_URL: ${{ vars.VITE_BASE_URL }} 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | permissions: 16 | contents: write 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Install pnpm 27 | uses: pnpm/action-setup@v4 28 | 29 | - name: Use Node.js 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version-file: '.nvmrc' 33 | cache: 'pnpm' 34 | 35 | - name: Cache node_modules 36 | uses: actions/cache@v4 37 | id: cache-node-modules 38 | with: 39 | path: node_modules 40 | key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} 41 | 42 | - name: Cache dist 43 | uses: actions/cache@v4 44 | id: cache-dist 45 | with: 46 | path: dist 47 | key: ${{ runner.os }}-build-${{ github.sha }} 48 | 49 | - name: Install 50 | run: pnpm install 51 | 52 | - name: Build 53 | run: pnpm build 54 | 55 | deploy: 56 | runs-on: ubuntu-latest 57 | needs: build 58 | 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v4 62 | 63 | - name: Restore dist cache 64 | uses: actions/cache@v4 65 | id: cache-dist 66 | with: 67 | path: dist 68 | key: ${{ runner.os }}-build-${{ github.sha }} 69 | 70 | - name: Deploy 🚀 71 | uses: JamesIves/github-pages-deploy-action@v4 72 | with: 73 | branch: gh-pages 74 | clean: true 75 | single-commit: true 76 | folder: dist -------------------------------------------------------------------------------- /.github/_workflow-samples/deploy-s3.yml: -------------------------------------------------------------------------------- 1 | 2 | # Deploy the site to AWS S3 on a push to the 'main' branch 3 | 4 | name: Deploy S3 5 | 6 | on: 7 | push: 8 | branches: 9 | - 'main' 10 | 11 | concurrency: 12 | group: ${{ github.workflow }}-${{ github.ref }} 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | env: 19 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 20 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Install pnpm 27 | uses: pnpm/action-setup@v4 28 | 29 | - name: Use Node.js ${{ env.NODE }} 30 | uses: actions/setup-node@v4 31 | with: 32 | node-version-file: '.nvmrc' 33 | cache: 'pnpm' 34 | 35 | - name: Cache node_modules 36 | uses: actions/cache@v4 37 | id: cache-node-modules 38 | with: 39 | path: node_modules 40 | key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} 41 | 42 | - name: Cache dist 43 | uses: actions/cache@v4 44 | id: cache-dist 45 | with: 46 | path: dist 47 | key: ${{ runner.os }}-build-${{ github.sha }} 48 | 49 | - name: Install 50 | run: pnpm install 51 | 52 | - name: Build 53 | run: pnpm build 54 | 55 | deploy: 56 | runs-on: ubuntu-latest 57 | needs: build 58 | 59 | steps: 60 | - name: Checkout 61 | uses: actions/checkout@v4 62 | 63 | - name: Restore dist cache 64 | uses: actions/cache@v4 65 | id: cache-dist 66 | with: 67 | path: dist 68 | key: ${{ runner.os }}-build-${{ github.sha }} 69 | 70 | # Action: https://github.com/marketplace/actions/s3-deploy 71 | - name: Deploy to S3 72 | uses: reggionick/s3-deploy@v4 73 | with: 74 | folder: dist 75 | bucket: ${{ secrets.S3_BUCKET }} 76 | bucket-region: ${{ secrets.S3_BUCKET_REGION }} 77 | dist-id: ${{ secrets.CLOUDFRONT_DISTRIBUTION_ID }} 78 | invalidation: / 79 | delete-removed: true 80 | no-cache: true 81 | private: true 82 | files-to-include: '{.*/**,**}' 83 | -------------------------------------------------------------------------------- /.github/workflows/checks.yml: -------------------------------------------------------------------------------- 1 | # This workflow performs basic checks: 2 | # 3 | # 1. run a preparation step to install and cache node modules 4 | # 2. once prep succeeds, lint and test run in parallel 5 | # 6 | # The checks only run on non-draft Pull Requests. They don't run on the main 7 | # branch prior to deploy. It's recommended to use branch protection to avoid 8 | # pushes straight to 'main'. 9 | 10 | name: Checks 11 | 12 | on: 13 | pull_request: 14 | types: 15 | - opened 16 | - synchronize 17 | - reopened 18 | - ready_for_review 19 | 20 | concurrency: 21 | group: ${{ github.workflow }}-${{ github.ref }} 22 | cancel-in-progress: true 23 | 24 | jobs: 25 | prep: 26 | if: github.event.pull_request.draft == false 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | 33 | - name: Install pnpm 34 | uses: pnpm/action-setup@v4 35 | 36 | - name: Use Node.js ${{ env.NODE }} 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version-file: '.nvmrc' 40 | cache: 'pnpm' 41 | 42 | - name: Cache node_modules 43 | uses: actions/cache@v4 44 | id: cache-node-modules 45 | with: 46 | path: node_modules 47 | key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} 48 | 49 | - name: Install 50 | run: pnpm install 51 | 52 | lint: 53 | needs: prep 54 | runs-on: ubuntu-latest 55 | 56 | steps: 57 | - name: Checkout 58 | uses: actions/checkout@v4 59 | 60 | - name: Install pnpm 61 | uses: pnpm/action-setup@v4 62 | 63 | - name: Use Node.js ${{ env.NODE }} 64 | uses: actions/setup-node@v4 65 | with: 66 | node-version-file: '.nvmrc' 67 | cache: 'pnpm' 68 | 69 | - name: Cache node_modules 70 | uses: actions/cache@v4 71 | id: cache-node-modules 72 | with: 73 | path: node_modules 74 | key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} 75 | 76 | - name: Install 77 | run: pnpm install 78 | 79 | - name: Lint 80 | run: pnpm lint 81 | 82 | test: 83 | needs: prep 84 | runs-on: ubuntu-latest 85 | 86 | steps: 87 | - name: Checkout 88 | uses: actions/checkout@v4 89 | 90 | - name: Install pnpm 91 | uses: pnpm/action-setup@v4 92 | 93 | - name: Use Node.js ${{ env.NODE }} 94 | uses: actions/setup-node@v4 95 | with: 96 | node-version-file: '.nvmrc' 97 | cache: 'pnpm' 98 | 99 | - name: Cache node_modules 100 | uses: actions/cache@v4 101 | id: cache-node-modules 102 | with: 103 | path: node_modules 104 | key: ${{ runner.os }}-build-${{ hashFiles('**/package.json') }} 105 | 106 | - name: Install 107 | run: pnpm install 108 | 109 | - name: Test 110 | run: pnpm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################ 2 | ############### .gitignore ################## 3 | ################################################ 4 | # 5 | # This file is only relevant if you are using git. 6 | # 7 | # Files which match the splat patterns below will 8 | # be ignored by git. This keeps random crap and 9 | # and sensitive credentials from being uploaded to 10 | # your repository. It allows you to configure your 11 | # app for your machine without accidentally 12 | # committing settings which will smash the local 13 | # settings of other developers on your team. 14 | # 15 | # Some reasonable defaults are included below, 16 | # but, of course, you should modify/extend/prune 17 | # to fit your needs! 18 | ################################################ 19 | 20 | app/scripts/time.json 21 | 22 | node_modules 23 | bower_components 24 | .sass-cache 25 | test/bower_components 26 | 27 | 28 | ################################################ 29 | # Node.js / NPM 30 | # 31 | # Common files generated by Node, NPM, and the 32 | # related ecosystem. 33 | ################################################ 34 | 35 | lib-cov 36 | *.seed 37 | *.log 38 | *.out 39 | *.pid 40 | npm-debug.log 41 | yarn-error.log 42 | .parcel-cache 43 | 44 | 45 | ################################################ 46 | # Apidocs 47 | # 48 | # Common files generated by apidocs and other docs 49 | ################################################ 50 | 51 | 52 | ################################################ 53 | # Miscellaneous 54 | # 55 | # Common files generated by text editors, 56 | # operating systems, file systems, etc. 57 | ################################################ 58 | 59 | *~ 60 | *# 61 | .DS_STORE 62 | .DS_Store 63 | .netbeans 64 | nbproject 65 | .idea 66 | .resources 67 | .node_history 68 | temp 69 | tmp 70 | .tmp 71 | dist 72 | parcel-bundle-reports -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true, 5 | "jsxSingleQuote": true, 6 | "printWidth": 80 7 | } -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "stylelint-config-standard", 4 | "stylelint-config-styled-components" 5 | ], 6 | "customSyntax": "postcss-styled-syntax", 7 | "rules": { 8 | "font-family-no-missing-generic-family-keyword": null, 9 | "no-descending-specificity": [ 10 | true, 11 | { 12 | "severity": "warning" 13 | } 14 | ] 15 | }, 16 | "ignoreFiles": [ 17 | "**/*.d.ts" 18 | ] 19 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Development Seed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # project-seed v8 2 | 3 | A basic starting point for web projects that uses vite as a Build System. 4 | 5 | Uses typescript and jest for testing 6 | 7 | ## Overview 8 | 9 | Steps to follow as soon as you download this structure to start a project: 10 | - [ ] Update `package.js` with data about the project (name, repo, license...) 11 | - [ ] If the license is known update `LICENSE` 12 | - [ ] Check `index.html` for bootstrap information that can be changed or removed. 13 | - [ ] Update the application title and description in `.env` 14 | - [ ] Remove unneeded images from the `static/meta` folder and replace the favicon with a project related one. 15 | - [ ] Update the modules to the most recent version. 16 | - [ ] **Delete this `README.md` and rename `_README.md`. Fill in the needed data. This is the most important task.** Others need to be able to know what the project is about and how to work with it. This can't be stressed enough. 17 | 18 | It's better to do this straight away so no traces of project-seed are ever pushed to github and just looks more professional. 19 | The values that are not immediately know should be left blank and filled ASAP. 20 | 21 | ## Vite for building 22 | 23 | [Vite](https://vite.dev/) is used to bundle all the needed assets for the application. 24 | There are two commands, both run via `pnpm` 25 | 26 | - `pnpm build` - clean & build everything and put it into dist folder 27 | - `pnpm serve` - serve the pages and utilize live reload on changes to fonts, images, scripts and HTML. 28 | 29 | ## Chakra UI for styling 30 | 31 | Project Seed uses [Chakra UI](https://chakra-ui.com/) for styling as a UI framework. It is a component library that provides a set of accessible and reusable components facilitating the development of web applications. 32 | 33 | If you don't want it, you just need to remove the `@chakra-ui/react` dependency from the `package.json` and remove the import from the `main.tsx` file. 34 | 35 | ### Configurations and environment variables 36 | 37 | At times, it may be necessary to include options/variables specific to `production`, `staging` or `local` in the code. To handle this, there you can use `.env` files. 38 | See Vite's documentation on [env variables](https://vite.dev/guide/env-and-mode.html#env-variables-and-modes). 39 | 40 | ## Github Actions for CI 41 | Testing and deployment is taken care of by Github Actions. It is set up to: 42 | 43 | 1. run checks (test & lint) on every non-draft Pull Request 44 | 2. build and deploy the application on pushes to the `main` branch 45 | 46 | To make sure that the site deploys with passing checks, branch protection should be set up for the `main` branch (`Require status checks to pass before merging`). 47 | 48 | Deploy is not set up by default, but the project contains [sample workflows](.github/_workflow-samples/README.md) that can be used to set it up. 49 | 50 | ## Linting 51 | 52 | Our [ESLint rules](.eslintrc) are based on `eslint:recommended` rules, with some custom options. To check linting errors run: 53 | 54 | npm run lint 55 | 56 | ## Tests 57 | 58 | Tests are setup using [Jest](https://jestjs.io/), and can be run with 59 | 60 | ``` 61 | npm run test 62 | ``` 63 | 64 | ## Coding style 65 | 66 | File [.editorconfig](.editorconfig) defines basic code styling rules, like indent sizes. 67 | 68 | [Prettier](https://prettier.io) is the recommended code formatter. Atom and VSCode have extensions supporting Prettier-ESLint integration, which will help maintain style consistency while following linting rules. 69 | 70 | ## Path alias 71 | 72 | Path alias allow you to define aliases for commonly used folders and avoid having very long file paths like `../../../component`. This also allows you to more easily move files around without worrying the imports will break. 73 | 74 | Paths are defined in the [package.json](./package.json) under `alias`. They start with a `$` and point to a folder. 75 | 76 | The following paths are predefined, but feel free to change them to whatever is convenient to your project needs. 77 | 78 | ```json 79 | "alias": { 80 | "$components": "~/app/scripts/components", 81 | "$styles": "~/app/scripts/styles", 82 | "$utils": "~/app/scripts/utils", 83 | "$test": "~/test" 84 | } 85 | ``` 86 | 87 | For example, to import a component from a file called `page-header` in the `"~/app/scripts/components"` folder, you'd just need to do `import Component from '$components/page-header'`. 88 | 89 | ## Pull Request templates 90 | 91 | Project seed comes with pull request templates to simplify and standardize the pull requests in the project. This [issue on the how repo](https://github.com/developmentseed/how/issues/360#issuecomment-1041292591) provides some context to how this works. 92 | 93 | To add more templates create them in the `.github/PULL_REQUEST_TEMPLATE` folder and link them in the [PULL_REQUEST_TEMPLATE.md](./.github/PULL_REQUEST_TEMPLATE.md) file. 94 | -------------------------------------------------------------------------------- /_README.md: -------------------------------------------------------------------------------- 1 | # {{Project name}} 2 | 3 | {{Description}} 4 | 5 | ## Installation and Usage 6 | The steps below will walk you through setting up your own instance of the project. 7 | 8 | ### Install Project Dependencies 9 | To set up the development environment for this website, you'll need to install the following on your system: 10 | 11 | - [Node](http://nodejs.org/) v22 (To manage multiple node versions we recommend [nvm](https://github.com/creationix/nvm)) 12 | - [pnpm](https://pnpm.io/) Install using corepack (`corepack enable pnpm`) 13 | 14 | ### Install Application Dependencies 15 | 16 | If you use [`nvm`](https://github.com/creationix/nvm), activate the desired Node version: 17 | 18 | ``` 19 | nvm install 20 | ``` 21 | 22 | Install Node modules: 23 | 24 | ``` 25 | pnpm install 26 | ``` 27 | 28 | ## Usage 29 | 30 | ### Config files 31 | Configuration is done using [dot.env](https://vite.dev/guide/env-and-mode#env-files) files. 32 | 33 | These files are used to simplify the configuration of the app and should not contain sensitive information. 34 | 35 | Run the project locally by copying the `.env` to `.env.local` and setting the following environment variables: 36 | 37 | 38 | | --- | --- | 39 | | `{{VARIABLE}}` | {{description}} | 40 | 41 | ### Starting the app 42 | 43 | ``` 44 | pnpm serve 45 | ``` 46 | Compiles the sass files, javascript, and launches the server making the site available at `http://localhost:9000/` 47 | The system will watch files and execute tasks whenever one of them changes. 48 | The site will automatically refresh since it is bundled with livereload. 49 | 50 | # Deployment 51 | To prepare the app for deployment run: 52 | 53 | ``` 54 | pnpm build 55 | ``` 56 | or 57 | ``` 58 | pnpm stage 59 | ``` 60 | This will package the app and place all the contents in the `dist` directory. 61 | The app can then be run by any web server. 62 | 63 | **When building the site for deployment provide the base url trough the `VITE_BASE_URL` environment variable. Omit the leading slash. (E.g. https://example.com)** 64 | -------------------------------------------------------------------------------- /app/main.tsx: -------------------------------------------------------------------------------- 1 | import { ChakraProvider } from '@chakra-ui/react'; 2 | import React, { useEffect } from 'react'; 3 | import { createRoot } from 'react-dom/client'; 4 | 5 | import system from './styles/theme'; 6 | 7 | // If using a router add the public url to the base path. 8 | const publicUrl = import.meta.env.VITE_BASE_URL || ''; 9 | // The ds.io prefix is used just to get the base path when no public url is set. 10 | const baseName = new URL( 11 | publicUrl.startsWith('http') 12 | ? publicUrl 13 | : `https://ds.io/${publicUrl.replace(/^\//, '')}` 14 | ).pathname; 15 | 16 | // Root component. 17 | function Root() { 18 | useEffect(() => { 19 | // Hide the welcome banner. 20 | const banner = document.querySelector('#welcome-banner')!; 21 | banner.classList.add('dismissed'); 22 | setTimeout(() => banner.remove(), 500); 23 | }, []); 24 | 25 | return ( 26 | 27 |

Hello World

28 |
29 | ); 30 | } 31 | 32 | const rootNode = document.querySelector('#app-container')!; 33 | const root = createRoot(rootNode); 34 | root.render(); 35 | -------------------------------------------------------------------------------- /app/media/layout/ds-logo-pos.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/styles/color-palette.ts: -------------------------------------------------------------------------------- 1 | import { rgba, tint, shade } from 'polished'; 2 | 3 | /** 4 | * Curry the polished rgba function to allow switching the parameters. 5 | */ 6 | const _rgba = (alpha: number) => (color: string) => rgba(color, alpha); 7 | 8 | const colorPaletteSettings = [ 9 | { 10 | code: '50', 11 | colorFn: tint(0.96) 12 | }, 13 | { 14 | code: '50a', 15 | colorFn: _rgba(0.04) 16 | }, 17 | { 18 | code: '100', 19 | colorFn: tint(0.92) 20 | }, 21 | { 22 | code: '100a', 23 | colorFn: _rgba(0.08) 24 | }, 25 | { 26 | code: '200', 27 | colorFn: tint(0.84) 28 | }, 29 | { 30 | code: '200a', 31 | colorFn: _rgba(0.16) 32 | }, 33 | { 34 | code: '300', 35 | colorFn: tint(0.68) 36 | }, 37 | { 38 | code: '300a', 39 | colorFn: _rgba(0.32) 40 | }, 41 | { 42 | code: '400', 43 | colorFn: tint(0.36) 44 | }, 45 | { 46 | code: '400a', 47 | colorFn: _rgba(0.64) 48 | }, 49 | { 50 | code: '500', 51 | colorFn: (v: string) => v 52 | }, 53 | { 54 | code: '600', 55 | colorFn: shade(0.16) 56 | }, 57 | { 58 | code: '700', 59 | colorFn: shade(0.32) 60 | }, 61 | { 62 | code: '800', 63 | colorFn: shade(0.48) 64 | }, 65 | { 66 | code: '900', 67 | colorFn: shade(0.64) 68 | } 69 | ]; 70 | 71 | /** 72 | * Creates a color palette base off of the provided base color including 73 | * lightened/darkened/transparent versions of that color. 74 | * 75 | * Uses a scale from 50 - 900 to indicate the color value. Values lower than 500 76 | * are lightened, above 500 are darkened and values ending with `a` have a alpha 77 | * channel. 78 | * 79 | * List of returned colors: 80 | * name.50 Lightened 96% 81 | * name.50a Opacity 4% 82 | * name.100 Lightened 92% 83 | * name.100a Opacity 8% 84 | * name.200 Lightened 84% 85 | * name.200a Opacity 16% 86 | * name.300 Lightened 68% 87 | * name.300a Opacity 32% 88 | * name.400 Lightened 36% 89 | * name.400a Opacity 64% 90 | * name.500 Same as base color 91 | * name.600 Darkened 16% 92 | * name.700 Darkened 32% 93 | * name.800 Darkened 48% 94 | * name.900 Darkened 64% 95 | * 96 | * @param {string} name Name of the color variable 97 | * @param {string} baseColor Base color for the palette. Used as middle color 98 | * with value 500. 99 | * 100 | * @returns object 101 | */ 102 | export function createColorPalette(baseColor: string) { 103 | return colorPaletteSettings.reduce( 104 | (acc, c) => ({ 105 | ...acc, 106 | [c.code]: { value: c.colorFn(baseColor) } 107 | }), 108 | {} 109 | ); 110 | } 111 | -------------------------------------------------------------------------------- /app/styles/theme.ts: -------------------------------------------------------------------------------- 1 | import { createSystem, defaultConfig, defineConfig } from '@chakra-ui/react'; 2 | import { createColorPalette } from './color-palette'; 3 | 4 | const config = defineConfig({ 5 | theme: { 6 | tokens: { 7 | colors: { 8 | primary: createColorPalette('#1E7BC6'), 9 | secondary: createColorPalette('#5FAD56'), 10 | base: createColorPalette('#2B2D42'), 11 | danger: createColorPalette('#D65108'), 12 | warning: createColorPalette('#EFA00B'), 13 | success: createColorPalette('#5FAD56'), 14 | info: createColorPalette('#1E7BC6'), 15 | surface: createColorPalette('#FFF') 16 | } 17 | } 18 | } 19 | }); 20 | 21 | export default createSystem(defaultConfig, config); 22 | -------------------------------------------------------------------------------- /app/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from 'globals'; 2 | import pluginJs from '@eslint/js'; 3 | import tseslint from 'typescript-eslint'; 4 | import pluginReact from 'eslint-plugin-react'; 5 | import reactHooks from 'eslint-plugin-react-hooks'; 6 | import reactRefresh from 'eslint-plugin-react-refresh'; 7 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 8 | 9 | /** @type {import('eslint').Linter.Config[]} */ 10 | export default [ 11 | { 12 | files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'], 13 | settings: { react: { version: 'detect' } }, 14 | languageOptions: { ecmaVersion: 2020, globals: globals.browser }, 15 | plugins: { 'react-hooks': reactHooks, 'react-refresh': reactRefresh } 16 | }, 17 | pluginJs.configs.recommended, 18 | ...tseslint.configs.recommended, 19 | pluginReact.configs.flat.recommended, 20 | eslintPluginPrettierRecommended, 21 | { 22 | name: 'Custom Rules ', 23 | rules: { 24 | 'no-console': 2, 25 | 'prefer-promise-reject-errors': 0, 26 | // 'import/order': 2, 27 | 'react/button-has-type': 2, 28 | 'react/jsx-closing-bracket-location': 2, 29 | 'react/jsx-closing-tag-location': 2, 30 | 'react/jsx-curly-spacing': 2, 31 | 'react/jsx-curly-newline': 2, 32 | 'react/jsx-equals-spacing': 2, 33 | 'react/jsx-max-props-per-line': [2, { maximum: 1, when: 'multiline' }], 34 | 'react/jsx-first-prop-new-line': 2, 35 | 'react/jsx-curly-brace-presence': [ 36 | 2, 37 | { props: 'never', children: 'never' } 38 | ], 39 | 'react/jsx-pascal-case': 2, 40 | 'react/jsx-props-no-multi-spaces': 2, 41 | 'react/jsx-tag-spacing': [2, { beforeClosing: 'never' }], 42 | 'react/jsx-wrap-multilines': 2, 43 | 'react/no-array-index-key': 2, 44 | 'react/no-typos': 2, 45 | 'react/no-unused-prop-types': 2, 46 | 'react/no-unused-state': 2, 47 | 'react/self-closing-comp': 2, 48 | 'react/style-prop-object': 2, 49 | 'react/void-dom-elements-no-children': 2, 50 | 'react/function-component-definition': [ 51 | 2, 52 | { namedComponents: ['function-declaration', 'arrow-function'] } 53 | ], 54 | 'react-hooks/rules-of-hooks': 2, // Checks rules of Hooks 55 | // 'react-hooks/exhaustive-deps': 1, // Checks effect dependencies 56 | // 'fp/no-mutating-methods': 1, 57 | '@typescript-eslint/no-explicit-any': 'warn' 58 | } 59 | } 60 | ]; 61 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | %VITE_APP_TITLE% 17 | 18 | 19 | 20 | 58 | 59 | 60 | 61 | 62 |
63 |
64 | 69 |

Development Seed logotype

70 |

In the beginning the Universe was created. 71 |

72 |

This has made a lot of people very angry and been widely regarded as a bad move.

73 |
74 |
75 | 76 | 77 |
78 | 79 |
80 | 81 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json'); 2 | 3 | /* 4 | * For a detailed explanation regarding each configuration property, visit: 5 | * https://jestjs.io/docs/configuration 6 | */ 7 | 8 | module.exports = { 9 | // All imported modules in your tests should be mocked automatically 10 | // automock: false, 11 | 12 | // Stop running tests after `n` failures 13 | // bail: 0, 14 | 15 | // The directory where Jest should store its cached dependency information 16 | // cacheDirectory: "/private/var/folders/bz/vry80ww15sg533jytwfj7fdc0000gn/T/jest_dx", 17 | 18 | // Automatically clear mock calls, instances and results before every test 19 | // clearMocks: false, 20 | 21 | // Indicates whether the coverage information should be collected while executing the test 22 | // collectCoverage: false, 23 | 24 | // An array of glob patterns indicating a set of files for which coverage information should be collected 25 | // collectCoverageFrom: undefined, 26 | 27 | // The directory where Jest should output its coverage files 28 | // coverageDirectory: undefined, 29 | 30 | // An array of regexp pattern strings used to skip coverage collection 31 | // coveragePathIgnorePatterns: [ 32 | // "/node_modules/" 33 | // ], 34 | 35 | // Indicates which provider should be used to instrument code for coverage 36 | // coverageProvider: "babel", 37 | 38 | // A list of reporter names that Jest uses when writing coverage reports 39 | // coverageReporters: [ 40 | // "json", 41 | // "text", 42 | // "lcov", 43 | // "clover" 44 | // ], 45 | 46 | // An object that configures minimum threshold enforcement for coverage results 47 | // coverageThreshold: undefined, 48 | 49 | // A path to a custom dependency extractor 50 | // dependencyExtractor: undefined, 51 | 52 | // Make calling deprecated APIs throw helpful error messages 53 | // errorOnDeprecated: false, 54 | 55 | // Force coverage collection from ignored files using an array of glob patterns 56 | // forceCoverageMatch: [], 57 | 58 | // A path to a module which exports an async function that is triggered once before all test suites 59 | // globalSetup: undefined, 60 | 61 | // A path to a module which exports an async function that is triggered once after all test suites 62 | // globalTeardown: undefined, 63 | 64 | // A set of global variables that need to be available in all test environments 65 | globals: { 66 | NODE_ENV: 'test' 67 | }, 68 | 69 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 70 | // maxWorkers: "50%", 71 | 72 | // An array of directory names to be searched recursively up from the requiring module's location 73 | moduleDirectories: ['node_modules'], 74 | 75 | // An array of file extensions your modules use 76 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json', 'node', 'css'], 77 | 78 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 79 | // This has to be kept in sync with the alias field of package.json 80 | moduleNameMapper: { 81 | // To simplify keeping the alias in sync the code below converts the aliases 82 | // defined in the package.json to module mappings: 83 | // From: 84 | // "$styles": "~/app/scripts/styles" 85 | // To: 86 | // '^\\$styles(.*)$': '/app/scripts/styles$1' 87 | ...Object.entries(pkg.alias ?? {}).reduce((acc, [key, value]) => { 88 | return value.startsWith('~/') 89 | ? { 90 | ...acc, 91 | [`^\\${key}(.*)$`]: `${value.substring(1)}$1` 92 | } 93 | : acc; 94 | }, {}), 95 | '.+\\.(css|styl|less|sass|scss)$': 96 | '/node_modules/jest-css-modules-transform' 97 | }, 98 | 99 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 100 | // modulePathIgnorePatterns: [], 101 | 102 | // Activates notifications for test results 103 | // notify: false, 104 | 105 | // An enum that specifies notification mode. Requires { notify: true } 106 | // notifyMode: "failure-change", 107 | 108 | // A preset that is used as a base for Jest's configuration 109 | preset: 'ts-jest', 110 | 111 | // Run tests from one or more projects 112 | // projects: undefined, 113 | 114 | // Use this configuration option to add custom reporters to Jest 115 | // reporters: undefined, 116 | 117 | // Automatically reset mock state before every test 118 | // resetMocks: false, 119 | 120 | // Reset the module registry before running each individual test 121 | // resetModules: false, 122 | 123 | // A path to a custom resolver 124 | // resolver: undefined, 125 | 126 | // Automatically restore mock state and implementation before every test 127 | // restoreMocks: false, 128 | 129 | // The root directory that Jest should scan for tests and modules within 130 | // rootDir: undefined, 131 | 132 | // A list of paths to directories that Jest should use to search for files in 133 | // roots: [ 134 | // "" 135 | // ], 136 | 137 | // Allows you to use a custom runner instead of Jest's default test runner 138 | // runner: "jest-runner", 139 | 140 | // The paths to modules that run some code to configure or set up the testing environment before each test 141 | // setupFiles: [], 142 | 143 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 144 | // setupFilesAfterEnv: [], 145 | 146 | // The number of seconds after which a test is considered as slow and reported as such in the results. 147 | // slowTestThreshold: 5, 148 | 149 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 150 | // snapshotSerializers: [], 151 | 152 | // The test environment that will be used for testing 153 | testEnvironment: 'jsdom', 154 | 155 | // Options that will be passed to the testEnvironment 156 | // testEnvironmentOptions: {}, 157 | 158 | // Adds a location field to test results 159 | // testLocationInResults: false, 160 | 161 | // The glob patterns Jest uses to detect test files 162 | // testMatch: [ 163 | // "**/__tests__/**/*.[jt]s?(x)", 164 | // "**/?(*.)+(spec|test).[tj]s?(x)" 165 | // ], 166 | 167 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 168 | // testPathIgnorePatterns: [ 169 | // "/node_modules/" 170 | // ], 171 | 172 | // The regexp pattern or array of patterns that Jest uses to detect test files 173 | // testRegex: [], 174 | 175 | // This option allows the use of a custom results processor 176 | // testResultsProcessor: undefined, 177 | 178 | // This option allows use of a custom test runner 179 | // testRunner: "jest-circus/runner", 180 | 181 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 182 | // testURL: "http://localhost", 183 | 184 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 185 | // timers: "real", 186 | 187 | // A map from regular expressions to paths to transformers 188 | transform: { 189 | '^.+\\.(js|jsx)$': 'babel-jest', 190 | '^.+\\.(ts|tsx)?$': ['ts-jest', { tsconfig: 'tsconfig.app.json' }] 191 | }, 192 | 193 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 194 | // transformIgnorePatterns: [ 195 | // "/node_modules/", 196 | // "\\.pnp\\.[^\\/]+$" 197 | // ], 198 | 199 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 200 | // unmockedModulePathPatterns: undefined, 201 | 202 | // Indicates whether each individual test should be reported during the run 203 | verbose: true, 204 | 205 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 206 | // watchPathIgnorePatterns: [], 207 | 208 | // Whether to use watchman for file crawling 209 | // watchman: true, 210 | setupFilesAfterEnv: ['/jest.setup.ts'] 211 | }; 212 | -------------------------------------------------------------------------------- /jest.setup.ts: -------------------------------------------------------------------------------- 1 | // jest.setup.ts 2 | import '@testing-library/jest-dom'; 3 | 4 | // implementation of structuredClone polyfill 5 | 6 | if (typeof global.structuredClone !== 'function') { 7 | global.structuredClone = function structuredClone(value) { 8 | if (value === null || value === undefined) { 9 | return value; 10 | } 11 | 12 | try { 13 | // For objects and arrays, use JSON methods 14 | if (typeof value === 'object') { 15 | return JSON.parse(JSON.stringify(value)); 16 | } 17 | 18 | // For primitive values, return directly 19 | return value; 20 | } catch (error) { 21 | // eslint-disable-next-line no-console 22 | console.warn('structuredClone polyfill failed:', error); 23 | 24 | // Returns a shallow copy as fallback 25 | return Array.isArray(value) ? [...value] : { ...value }; 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "project-seed", 3 | "description": "Starter application by Development Seed", 4 | "version": "8.0.1", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/developmentseed/project-seed.git" 8 | }, 9 | "author": { 10 | "name": "Development Seed", 11 | "url": "https://developmentseed.org" 12 | }, 13 | "license": "MIT", 14 | "bugs": { 15 | "url": "https://github.com/developmentseed/project-seed/issues" 16 | }, 17 | "homepage": "https://github.com/developmentseed/project-seed", 18 | "scripts": { 19 | "serve": "pnpm clean && NODE_ENV=development vite", 20 | "build": "pnpm clean && NODE_ENV=production tsc -b && vite build", 21 | "stage": "pnpm clean && NODE_ENV=staging tsc -b && vite build", 22 | "clean": "rm -rf dist node_modules/.vite", 23 | "lint": "pnpm lint:scripts", 24 | "lint:scripts": "eslint app/", 25 | "ts-check": "npx tsc --noEmit --skipLibCheck", 26 | "test": "jest" 27 | }, 28 | "engines": { 29 | "node": "22.x" 30 | }, 31 | "browserslist": "> 0.5%, last 2 versions, not dead", 32 | "devDependencies": { 33 | "@eslint/js": "^9.21.0", 34 | "@types/babel__core": "^7", 35 | "@types/jest": "^29.5.14", 36 | "@types/node": "^22.10.7", 37 | "@types/react": "^19.0.10", 38 | "@types/react-dom": "^19.0.4", 39 | "@vitejs/plugin-react": "^4.3.4", 40 | "babel-jest": "^29.7.0", 41 | "eslint": "^9.21.0", 42 | "eslint-config-prettier": "^10.0.1", 43 | "eslint-plugin-prettier": "^5.2.2", 44 | "eslint-plugin-react": "^7.37.4", 45 | "eslint-plugin-react-hooks": "^5.1.0", 46 | "eslint-plugin-react-refresh": "^0.4.19", 47 | "globals": "^15.15.0", 48 | "jest": "^29.7.0", 49 | "jest-environment-jsdom": "^29.7.0", 50 | "portscanner": "^2.2.0", 51 | "prettier": "^3.4.2", 52 | "ts-jest": "^29.2.5", 53 | "ts-node": "^10.9.2", 54 | "typescript": "~5.7.2", 55 | "typescript-eslint": "^8.24.1", 56 | "vite": "^6.2.0" 57 | }, 58 | "dependencies": { 59 | "@chakra-ui/react": "^3.8.1", 60 | "@emotion/react": "^11.14.0", 61 | "@testing-library/jest-dom": "^6.6.3", 62 | "@testing-library/react": "^16.2.0", 63 | "@testing-library/user-event": "^14.6.0", 64 | "@types/react": "^19.0.0", 65 | "@types/react-dom": "^19.0.4", 66 | "next-themes": "^0.4.4", 67 | "polished": "^4.3.1", 68 | "react": "^18.0.0", 69 | "react-dom": "^18.0.0", 70 | "react-icons": "^5.5.0" 71 | }, 72 | "alias": { 73 | "$components": "~/app/components", 74 | "$styles": "~/app/styles", 75 | "$utils": "~/app/utils", 76 | "$hooks": "~/app/hooks", 77 | "$pages": "~/app/pages", 78 | "$test": "~/test" 79 | }, 80 | "packageManager": "pnpm@10.6.4+sha512.da3d715bfd22a9a105e6e8088cfc7826699332ded60c423b14ec613a185f1602206702ff0fe4c438cb15c979081ce4cb02568e364b15174503a63c7a8e2a5f6c" 81 | } 82 | -------------------------------------------------------------------------------- /public/meta/android-chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/project-seed/2e6b80945bd3b03da995660bc45900dd28157a83/public/meta/android-chrome.png -------------------------------------------------------------------------------- /public/meta/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/project-seed/2e6b80945bd3b03da995660bc45900dd28157a83/public/meta/apple-touch-icon.png -------------------------------------------------------------------------------- /public/meta/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/project-seed/2e6b80945bd3b03da995660bc45900dd28157a83/public/meta/favicon.png -------------------------------------------------------------------------------- /public/meta/meta-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/project-seed/2e6b80945bd3b03da995660bc45900dd28157a83/public/meta/meta-image.png -------------------------------------------------------------------------------- /test/example.test.ts: -------------------------------------------------------------------------------- 1 | describe('Main test suite', () => { 2 | it('should pass this demo test', () => { 3 | expect(true).toBe(true); 4 | }); 5 | }); -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | "paths": { 18 | /* Specify a set of entries that re-map imports to additional lookup locations. */ 19 | "$components/*": ["./app/components/*"], 20 | "$utils/*": ["./app/utils/*"], 21 | "$styles/*": ["./app/styles/*"], 22 | "$hooks/*": ["./app/hooks/*"], 23 | "$pages/*": ["./app/pages/*"] 24 | }, 25 | 26 | /* Linting */ 27 | "strict": true, 28 | "noUnusedLocals": true, 29 | "noUnusedParameters": true, 30 | "noFallthroughCasesInSwitch": true, 31 | "noUncheckedSideEffectImports": true 32 | }, 33 | "include": ["app"] 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.mts"] 24 | } 25 | -------------------------------------------------------------------------------- /vite-plugin-port-scanner.ts: -------------------------------------------------------------------------------- 1 | // @ts-expect-error No types 2 | import portscanner from 'portscanner'; 3 | import { PluginOption } from 'vite'; 4 | 5 | export default function vitePortScanner() { 6 | return { 7 | name: 'vite-port-scanner-plugin', 8 | 9 | // Vite config hooks 10 | async config(config, { command }) { 11 | if (command === 'serve') { 12 | const startPort = config.server?.port || 9000; 13 | const port = await portscanner.findAPortNotInUse( 14 | startPort, 15 | startPort + 100, 16 | 'localhost' 17 | ); 18 | if (port !== startPort) { 19 | // eslint-disable-next-line no-console 20 | console.warn( 21 | ` Port ${startPort} is busy. Using port ${port} instead.` 22 | ); 23 | } 24 | config.server = { ...(config.server || {}), port }; 25 | } 26 | } 27 | } as PluginOption; 28 | } 29 | -------------------------------------------------------------------------------- /vite.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | import path from 'path'; 4 | 5 | import vitePortScanner from './vite-plugin-port-scanner'; 6 | import pkg from './package.json'; 7 | 8 | const alias = Object.entries(pkg.alias).reduce((acc, [key, value]) => { 9 | return { 10 | ...acc, 11 | [key]: path.resolve(__dirname, value.replace('~/', './')) 12 | }; 13 | }, {}); 14 | 15 | 16 | // https://vite.dev/config/ 17 | export default defineConfig({ 18 | plugins: [react(), vitePortScanner()], 19 | define: { 20 | APP_VERSION: JSON.stringify(pkg.version) 21 | }, 22 | resolve: { 23 | alias 24 | } 25 | }); 26 | --------------------------------------------------------------------------------