Migrate workspace to Vite+

This commit is contained in:
Yangshun
2026-03-20 15:59:43 +08:00
parent fa1ec6c7ec
commit 155e636829
15 changed files with 1175 additions and 316 deletions

View File

@@ -1,5 +1,5 @@
# Copied from https://github.com/facebook/docusaurus/blob/main/.github/workflows/lint.yml
name: Lint
# Workspace validation using Vite+
name: Check
on:
pull_request:
@@ -13,33 +13,22 @@ concurrency:
permissions:
contents: read
env:
DATABASE_URL: 'postgresql://postgres:password@localhost:5432/postgres'
GITHUB_CLIENT_ID: '1234'
GITHUB_CLIENT_SECRET: 'abcd'
NEXTAUTH_SECRET: 'efgh'
NEXTAUTH_URL: 'http://localhost:3000'
NODE_ENV: test
SUPABASE_ANON_KEY: 'ijkl'
SUPABASE_URL: 'https://abcd.supabase.co'
jobs:
lint:
name: Lint
check:
name: Check
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v4
- name: Set up Node
uses: actions/setup-node@v6
- name: Set up Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version: '25.8.1'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
cache: true
- name: Installation
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm lint
run: vp install
- name: Check
run: vp check
- name: Test
run: vp test

View File

@@ -1,5 +1,5 @@
# Copied from https://github.com/facebook/docusaurus/blob/main/.github/workflows/lint.yml
name: Typecheck
# Framework builds using Vite+ task orchestration
name: Build
on:
pull_request:
@@ -13,26 +13,32 @@ concurrency:
permissions:
contents: read
env:
DATABASE_URL: 'postgresql://postgres:password@localhost:5432/postgres'
GITHUB_CLIENT_ID: '1234'
GITHUB_CLIENT_SECRET: 'abcd'
NEXTAUTH_SECRET: 'efgh'
NEXTAUTH_URL: 'http://localhost:3000'
NODE_ENV: test
SUPABASE_ANON_KEY: 'ijkl'
SUPABASE_URL: 'https://abcd.supabase.co'
jobs:
tsc:
name: Typecheck
build:
name: Build
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v4
- name: Set up Node
uses: actions/setup-node@v6
- name: Set up Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version: '25.8.1'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
cache: true
- name: Installation
run: pnpm install --frozen-lockfile
# Build the shared types in dependent packages.
- name: Build dependencies
run: pnpm turbo run build --filter=ui
- name: Typecheck
run: pnpm tsc
run: vp install
- name: Build website
run: vp run --filter @tih/website build
- name: Build portal
run: vp run --filter @tih/portal build

View File

@@ -1,51 +0,0 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"bracketSameLine": true,
"printWidth": 80,
"proseWrap": "never",
"singleQuote": true,
"trailingComma": "all",
"sortPackageJson": false,
"overrides": [
{
"files": ["apps/portal/**/*.{js,jsx,ts,tsx}"],
"options": {
"sortTailwindcss": {
"config": "apps/portal/tailwind.config.cjs",
"functions": ["clsx"]
}
}
}
],
"ignorePatterns": [
"node_modules",
".vscode",
".next",
"build",
"dist",
"*.tsbuildinfo",
"*.gitignore",
"*.svg",
"*.lock",
"*.npmignore",
"*.sql",
"*.png",
"*.jpg",
"*.jpeg",
"*.gif",
"*.ico",
"*.sh",
"Dockerfile",
"Dockerfile.*",
".env",
".env.*",
"LICENSE",
"*.log",
".DS_Store",
".dockerignore",
"*.patch",
"*.toml",
"*.prisma",
"apps/website/experimental/domain/**/*.html"
]
}

View File

@@ -1,90 +0,0 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "react"],
"categories": {
"correctness": "error"
},
"settings": {
"next": {
"rootDir": ["apps/portal/"]
}
},
"ignorePatterns": [
".next/**",
".turbo/**",
".cache/**",
"dist/**",
"dist-ssr/**",
"coverage/**",
"public/dist/**",
"server/dist/**",
"apps/portal/build/**",
"apps/portal/out/**",
"apps/portal/prisma/**",
"apps/website/.docusaurus/**",
"apps/website/.cache-loader/**",
"apps/website/experimental/**"
],
"rules": {
"camelcase": ["error", { "properties": "never", "ignoreDestructuring": true }],
"capitalized-comments": [
"error",
"always",
{ "ignoreConsecutiveComments": true }
],
"curly": "error",
"eqeqeq": ["error", "smart"],
"func-names": ["error", "as-needed"],
"func-style": ["error", "declaration", { "allowArrowFunctions": true }],
"guard-for-in": "error",
"init-declarations": "error",
"no-console": ["error", { "allow": ["warn", "error", "info"] }],
"no-else-return": ["error", { "allowElseIf": false }],
"no-lonely-if": "error",
"no-shadow": "off",
"no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"operator-assignment": "error",
"prefer-const": "error",
"prefer-destructuring": ["error", { "object": true }],
"radix": "error",
"react/button-has-type": "error",
"react/display-name": "off",
"react/exhaustive-deps": "off",
"react/jsx-boolean-value": ["error", "always"],
"react/jsx-curly-brace-presence": [
"error",
{ "props": "never", "children": "never" }
],
"react/jsx-no-useless-fragment": "error",
"react/no-array-index-key": "error",
"react/no-unescaped-entities": "off",
"react/react-in-jsx-scope": "off",
"react/void-dom-elements-no-children": "error",
"typescript/array-type": [
"error",
{ "default": "generic", "readonly": "generic" }
],
"typescript/ban-ts-comment": "off",
"typescript/consistent-generic-constructors": ["error", "constructor"],
"typescript/consistent-indexed-object-style": ["error", "record"],
"typescript/consistent-type-definitions": ["error", "type"],
"typescript/consistent-type-imports": "error",
"typescript/dot-notation": "error",
"typescript/no-duplicate-enum-values": "error",
"typescript/no-explicit-any": "off",
"typescript/no-for-in-array": "error",
"typescript/prefer-optional-chain": "error",
"typescript/require-array-sort-compare": "error",
"typescript/restrict-plus-operands": "error"
},
"overrides": [
{
"files": ["apps/portal/**/*.{js,jsx,ts,tsx}"],
"plugins": ["typescript", "react", "nextjs"],
"rules": {
"nextjs/no-html-link-for-pages": "off",
"nextjs/no-img-element": "off"
}
}
]
}

1
.vite-hooks/pre-commit Executable file
View File

@@ -0,0 +1 @@
vp staged

76
AGENTS.md Normal file
View File

@@ -0,0 +1,76 @@
<!--VITE PLUS START-->
# Using Vite+, the Unified Toolchain for the Web
This project is using Vite+, a unified toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, Oxfmt, and Vite Task. Vite+ wraps runtime management, package management, and frontend tooling in a single global CLI called `vp`. Vite+ is distinct from Vite, but it invokes Vite through `vp dev` and `vp build`.
## Vite+ Workflow
`vp` is a global binary that handles the full development lifecycle. Run `vp help` to print a list of commands and `vp <command> --help` for information about a specific command.
### Start
- create - Create a new project from a template
- migrate - Migrate an existing project to Vite+
- config - Configure hooks and agent integration
- staged - Run linters on staged files
- install (`i`) - Install dependencies
- env - Manage Node.js versions
### Develop
- dev - Run the development server
- check - Run format, lint, and TypeScript type checks
- lint - Lint code
- fmt - Format code
- test - Run tests
### Execute
- run - Run monorepo tasks
- exec - Execute a command from local `node_modules/.bin`
- dlx - Execute a package binary without installing it as a dependency
- cache - Manage the task cache
### Build
- build - Build for production
- pack - Build libraries
- preview - Preview production build
### Manage Dependencies
Vite+ automatically detects and wraps the underlying package manager such as pnpm, npm, or Yarn through the `packageManager` field in `package.json` or package manager-specific lockfiles.
- add - Add packages to dependencies
- remove (`rm`, `un`, `uninstall`) - Remove packages from dependencies
- update (`up`) - Update packages to latest versions
- dedupe - Deduplicate dependencies
- outdated - Check for outdated packages
- list (`ls`) - List installed packages
- why (`explain`) - Show why a package is installed
- info (`view`, `show`) - View package information from the registry
- link (`ln`) / unlink - Manage local package links
- pm - Forward a command to the package manager
### Maintain
- upgrade - Update `vp` itself to the latest version
These commands map to their corresponding tools. For example, `vp dev --port 3000` runs Vite's dev server and works the same as Vite. `vp test` runs JavaScript tests through the bundled Vitest. The version of all tools can be checked using `vp --version`. This is useful when researching documentation, features, and bugs.
## Common Pitfalls
- **Using the package manager directly:** Do not use pnpm, npm, or Yarn directly. Vite+ can handle all package manager operations.
- **Always use Vite commands to run tools:** Don't attempt to run `vp vitest` or `vp oxlint`. They do not exist. Use `vp test` and `vp lint` instead.
- **Running scripts:** Vite+ built-in commands (`vp dev`, `vp build`, `vp test`, etc.) always run the Vite+ built-in tool, not any `package.json` script of the same name. To run a custom script that shares a name with a built-in command, use `vp run <script>`. For example, if you have a custom `dev` script that runs multiple services concurrently, run it with `vp run dev`, not `vp dev` (which always starts Vite's dev server).
- **Do not install Vitest, Oxlint, Oxfmt, or tsdown directly:** Vite+ wraps these tools. They must not be installed directly. You cannot upgrade these tools by installing their latest versions. Always use Vite+ commands.
- **Use Vite+ wrappers for one-off binaries:** Use `vp dlx` instead of package-manager-specific `dlx`/`npx` commands.
- **Import JavaScript modules from `vite-plus`:** Instead of importing from `vite` or `vitest`, all modules should be imported from the project's `vite-plus` dependency. For example, `import { defineConfig } from 'vite-plus';` or `import { expect, test, vi } from 'vite-plus/test';`. You must not install `vitest` to import test utilities.
- **Type-Aware Linting:** There is no need to install `oxlint-tsgolint`, `vp lint --type-aware` works out of the box.
## Review Checklist for Agents
- [ ] Run `vp install` after pulling remote changes and before getting started.
- [ ] Run `vp check` and `vp test` to validate changes.
<!--VITE PLUS END-->

View File

@@ -6,7 +6,7 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "oxlint next.config.mjs src",
"lint": "vp lint next.config.mjs src",
"tsc": "tsc",
"postinstall": "prisma generate",
"seed": "ts-node prisma/seed.ts",

View File

@@ -1,13 +1,13 @@
# Website
This website is built using Docusaurus 2, a modern static website generator. It is part of a [Turborepo](https://turborepo.org/) monorepo setup.
This website is built using Docusaurus 2, a modern static website generator. It is part of the repository's Vite+ monorepo setup.
### Installation
We use pnpm as the package manager. At the root of the repository, run `pnpm install` to install the monorepo dependencies.
Vite+ manages installs through the repository's declared package manager. At the root of the repository, run `vp install` to install the monorepo dependencies.
```sh
$ pnpm install
$ vp install
```
### Local Development
@@ -15,18 +15,17 @@ $ pnpm install
Also at the root of the repository, run:
```sh
$ pnpm dev:website
$ vp run dev
```
This command starts a local development server and opens up a browser window. Most changes made to JavaScript and Markdown files will be reflected instantly without having to restart the server.
### Build
Navigate to the `apps/website` directory.
From the repository root, run:
```sh
cd apps/website
$ pnpm build
$ vp run --filter @tih/website build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
@@ -34,7 +33,8 @@ This command generates static content into the `build` directory and can be serv
### Deployment
```
$ GIT_USER=<Your GitHub username> USE_SSH=1 pnpm deploy
$ cd apps/website
$ GIT_USER=<Your GitHub username> USE_SSH=1 vp run deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@@ -7,7 +7,7 @@
"start": "docusaurus start",
"dev": "docusaurus start",
"build": "docusaurus build",
"lint": "oxlint docusaurus.config.js sidebars.js src",
"lint": "vp lint docusaurus.config.js sidebars.js src",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy"
},

View File

@@ -324,7 +324,10 @@ function HowToUseSection() {
<HowToUseStep
index={4}
title={<>Prepare for the job</>}
contents={['How to choose between companies', 'Guide to engineering levels']}
contents={[
'How to choose between companies',
'Guide to engineering levels',
]}
ctaLink="/choosing-between-companies"
/>
</div>

View File

@@ -9,21 +9,15 @@
"apps/*"
],
"scripts": {
"build": "turbo build",
"ci": "pnpm lint && pnpm tsc",
"clean": "turbo clean",
"dev:portal": "turbo dev --filter=@tih/portal...",
"dev": "turbo dev --filter=@tih/website...",
"format": "oxfmt .",
"format:check": "oxfmt --check .",
"lint": "turbo lint",
"test": "turbo test",
"tsc": "turbo tsc"
"build": "vp run --cache -r build",
"ci": "vp check && vp test && vp run --cache -r build",
"clean": "vp cache clean",
"dev:portal": "vp run --filter @tih/portal... dev",
"dev": "vp run --filter @tih/website... dev",
"prepare": "vp config"
},
"devDependencies": {
"oxlint": "^1.56.0",
"oxfmt": "^0.41.0",
"turbo": "2.8.20"
"vite-plus": "catalog:"
},
"engines": {
"node": "25.8.1",

920
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,17 @@
packages:
- 'apps/*'
- 'packages/*'
catalog:
vite: npm:@voidzero-dev/vite-plus-core@latest
vitest: npm:@voidzero-dev/vite-plus-test@latest
vite-plus: latest
overrides:
vite: 'catalog:'
vitest: 'catalog:'
peerDependencyRules:
allowAny:
- vite
- vitest
allowedVersions:
vite: '*'
vitest: '*'

View File

@@ -1,32 +0,0 @@
{
"$schema": "https://turborepo.org/schema.json",
"tasks": {
"build": {
"outputs": [
"dist/**",
".next/**",
"build/**",
"api/**",
"public/build/**"
],
"dependsOn": ["^build"]
},
"test": {
"outputs": ["coverage/**"],
"dependsOn": []
},
"lint": {
"outputs": []
},
"tsc": {
"cache": true,
"outputs": []
},
"dev": {
"cache": false
},
"clean": {
"cache": false
}
}
}

189
vite.config.ts Normal file
View File

@@ -0,0 +1,189 @@
import { defineConfig } from 'vite-plus';
export default defineConfig({
staged: {
'*': 'vp check --fix',
},
test: {
passWithNoTests: true,
},
fmt: {
bracketSameLine: true,
printWidth: 80,
proseWrap: 'never',
singleQuote: true,
trailingComma: 'all',
sortPackageJson: false,
overrides: [
{
files: ['apps/portal/**/*.{js,jsx,ts,tsx}'],
options: {
sortTailwindcss: {
config: 'apps/portal/tailwind.config.cjs',
functions: ['clsx'],
},
},
},
],
ignorePatterns: [
'node_modules',
'.vscode',
'.next',
'build',
'dist',
'*.tsbuildinfo',
'*.gitignore',
'*.svg',
'*.lock',
'*.npmignore',
'*.sql',
'*.png',
'*.jpg',
'*.jpeg',
'*.gif',
'*.ico',
'*.sh',
'Dockerfile',
'Dockerfile.*',
'.env',
'.env.*',
'LICENSE',
'*.log',
'.DS_Store',
'.dockerignore',
'*.patch',
'*.toml',
'*.prisma',
'apps/website/experimental/domain/**/*.html',
],
},
lint: {
plugins: ['typescript', 'react'],
categories: {
correctness: 'error',
},
settings: {
next: {
rootDir: ['apps/portal/'],
},
},
ignorePatterns: [
'.next/**',
'.turbo/**',
'.cache/**',
'dist/**',
'dist-ssr/**',
'coverage/**',
'public/dist/**',
'server/dist/**',
'apps/portal/build/**',
'apps/portal/out/**',
'apps/portal/prisma/**',
'apps/website/.docusaurus/**',
'apps/website/.cache-loader/**',
'apps/website/experimental/**',
],
rules: {
camelcase: [
'error',
{
properties: 'never',
ignoreDestructuring: true,
},
],
'capitalized-comments': [
'error',
'always',
{
ignoreConsecutiveComments: true,
},
],
curly: 'error',
eqeqeq: ['error', 'smart'],
'func-names': ['error', 'as-needed'],
'func-style': [
'error',
'declaration',
{
allowArrowFunctions: true,
},
],
'guard-for-in': 'error',
'init-declarations': 'error',
'no-console': [
'error',
{
allow: ['warn', 'error', 'info'],
},
],
'no-else-return': [
'error',
{
allowElseIf: false,
},
],
'no-lonely-if': 'error',
'no-shadow': 'off',
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
},
],
'operator-assignment': 'error',
'prefer-const': 'error',
'prefer-destructuring': [
'error',
{
object: true,
},
],
radix: 'error',
'react/button-has-type': 'error',
'react/display-name': 'off',
'react/exhaustive-deps': 'off',
'react/jsx-boolean-value': ['error', 'always'],
'react/jsx-curly-brace-presence': [
'error',
{
props: 'never',
children: 'never',
},
],
'react/jsx-no-useless-fragment': 'error',
'react/no-array-index-key': 'error',
'react/no-unescaped-entities': 'off',
'react/react-in-jsx-scope': 'off',
'react/void-dom-elements-no-children': 'error',
'typescript/array-type': [
'error',
{
default: 'generic',
readonly: 'generic',
},
],
'typescript/ban-ts-comment': 'off',
'typescript/consistent-generic-constructors': ['error', 'constructor'],
'typescript/consistent-indexed-object-style': ['error', 'record'],
'typescript/consistent-type-definitions': ['error', 'type'],
'typescript/consistent-type-imports': 'error',
'typescript/dot-notation': 'error',
'typescript/no-duplicate-enum-values': 'error',
'typescript/no-explicit-any': 'off',
'typescript/no-for-in-array': 'error',
'typescript/prefer-optional-chain': 'error',
'typescript/require-array-sort-compare': 'error',
'typescript/restrict-plus-operands': 'error',
},
overrides: [
{
files: ['apps/portal/**/*.{js,jsx,ts,tsx}'],
plugins: ['typescript', 'react', 'nextjs'],
rules: {
'nextjs/no-html-link-for-pages': 'off',
'nextjs/no-img-element': 'off',
},
},
],
},
});