@pauldvlp/vp-react-ts-nestjs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/index.js +188 -0
  4. package/package.json +46 -0
  5. package/template/README.md +122 -0
  6. package/template/_dockerignore +8 -0
  7. package/template/_gitignore +30 -0
  8. package/template/apps/api/Dockerfile +32 -0
  9. package/template/apps/api/_env.example +3 -0
  10. package/template/apps/api/_gitignore +8 -0
  11. package/template/apps/api/package.json +33 -0
  12. package/template/apps/api/src/app.module.ts +33 -0
  13. package/template/apps/api/src/common/zod-validation.pipe.ts +16 -0
  14. package/template/apps/api/src/config/config.module.ts +11 -0
  15. package/template/apps/api/src/config/env.ts +14 -0
  16. package/template/apps/api/src/health/health.controller.ts +9 -0
  17. package/template/apps/api/src/health/health.module.ts +8 -0
  18. package/template/apps/api/src/items/items.controller.ts +25 -0
  19. package/template/apps/api/src/items/items.module.ts +10 -0
  20. package/template/apps/api/src/items/items.service.spec.ts +26 -0
  21. package/template/apps/api/src/items/items.service.ts +24 -0
  22. package/template/apps/api/src/main.ts +30 -0
  23. package/template/apps/api/tsconfig.json +28 -0
  24. package/template/apps/api/vite.config.ts +35 -0
  25. package/template/apps/web/_gitignore +24 -0
  26. package/template/apps/web/index.html +13 -0
  27. package/template/apps/web/package.json +27 -0
  28. package/template/apps/web/public/favicon.svg +4 -0
  29. package/template/apps/web/src/App.tsx +48 -0
  30. package/template/apps/web/src/main.tsx +9 -0
  31. package/template/apps/web/tsconfig.app.json +31 -0
  32. package/template/apps/web/tsconfig.json +4 -0
  33. package/template/apps/web/tsconfig.node.json +23 -0
  34. package/template/apps/web/vite.config.ts +45 -0
  35. package/template/package.json +26 -0
  36. package/template/packages/contracts/package.json +24 -0
  37. package/template/packages/contracts/src/index.ts +18 -0
  38. package/template/packages/contracts/tsconfig.json +19 -0
  39. package/template/packages/contracts/vite.config.ts +20 -0
  40. package/template/pnpm-workspace.yaml +29 -0
  41. package/template/tsconfig.json +9 -0
  42. package/template/vite.config.ts +24 -0
@@ -0,0 +1,8 @@
1
+ import { Module } from '@nestjs/common'
2
+
3
+ import { HealthController } from './health.controller'
4
+
5
+ @Module({
6
+ controllers: [HealthController]
7
+ })
8
+ export class HealthModule {}
@@ -0,0 +1,25 @@
1
+ import { Body, Controller, Get, Param, Post } from '@nestjs/common'
2
+ import { createItemSchema, type CreateItem, type Item } from '@app/contracts'
3
+
4
+ import { ZodValidationPipe } from '../common/zod-validation.pipe'
5
+ import { ItemsService } from './items.service'
6
+
7
+ @Controller('items')
8
+ export class ItemsController {
9
+ constructor(private readonly items: ItemsService) {}
10
+
11
+ @Get()
12
+ findAll(): Item[] {
13
+ return this.items.findAll()
14
+ }
15
+
16
+ @Get(':id')
17
+ findOne(@Param('id') id: string): Item {
18
+ return this.items.findOne(id)
19
+ }
20
+
21
+ @Post()
22
+ create(@Body(new ZodValidationPipe(createItemSchema)) body: CreateItem): Item {
23
+ return this.items.create(body)
24
+ }
25
+ }
@@ -0,0 +1,10 @@
1
+ import { Module } from '@nestjs/common'
2
+
3
+ import { ItemsController } from './items.controller'
4
+ import { ItemsService } from './items.service'
5
+
6
+ @Module({
7
+ controllers: [ItemsController],
8
+ providers: [ItemsService]
9
+ })
10
+ export class ItemsModule {}
@@ -0,0 +1,26 @@
1
+ import { Test } from '@nestjs/testing'
2
+ import { beforeEach, describe, expect, it } from 'vite-plus/test'
3
+
4
+ import { ItemsService } from './items.service'
5
+
6
+ // Building the module through Nest's DI container is the real proof that decorator metadata is being
7
+ // emitted by vite-plus's Oxc transform — without it, `Test.createTestingModule(...).compile()` would throw.
8
+ describe('ItemsService', () => {
9
+ let service: ItemsService
10
+
11
+ beforeEach(async () => {
12
+ const moduleRef = await Test.createTestingModule({ providers: [ItemsService] }).compile()
13
+ service = moduleRef.get(ItemsService)
14
+ })
15
+
16
+ it('creates and lists items', () => {
17
+ const created = service.create({ name: 'first' })
18
+ expect(created.id).toBeTruthy()
19
+ expect(service.findAll()).toHaveLength(1)
20
+ expect(service.findOne(created.id)).toEqual(created)
21
+ })
22
+
23
+ it('throws for an unknown id', () => {
24
+ expect(() => service.findOne('nope')).toThrow()
25
+ })
26
+ })
@@ -0,0 +1,24 @@
1
+ import { Injectable, NotFoundException } from '@nestjs/common'
2
+ import type { CreateItem, Item } from '@app/contracts'
3
+
4
+ /** A throwaway in-memory store so the template runs with zero infra. Swap for a real repository. */
5
+ @Injectable()
6
+ export class ItemsService {
7
+ private readonly items: Item[] = []
8
+
9
+ findAll(): Item[] {
10
+ return this.items
11
+ }
12
+
13
+ findOne(id: string): Item {
14
+ const item = this.items.find((i) => i.id === id)
15
+ if (!item) throw new NotFoundException(`Item ${id} not found`)
16
+ return item
17
+ }
18
+
19
+ create(input: CreateItem): Item {
20
+ const item: Item = { id: crypto.randomUUID(), name: input.name, createdAt: new Date().toISOString() }
21
+ this.items.push(item)
22
+ return item
23
+ }
24
+ }
@@ -0,0 +1,30 @@
1
+ import 'reflect-metadata'
2
+
3
+ import { NestFactory } from '@nestjs/core'
4
+ import { Logger } from 'nestjs-pino'
5
+ // __SWAGGER_IMPORT__
6
+ import { AppModule } from './app.module'
7
+ import { ENV, type Env } from './config/env'
8
+
9
+ async function bootstrap() {
10
+ const app = await NestFactory.create(AppModule, { bufferLogs: true })
11
+ app.useLogger(app.get(Logger))
12
+ app.setGlobalPrefix('api')
13
+ // __SWAGGER_SETUP__
14
+ const env = app.get<Env>(ENV)
15
+ await app.listen(env.PORT)
16
+ return app
17
+ }
18
+
19
+ const app = bootstrap()
20
+
21
+ // Clean restart under `vite-node --watch`: `accept()` makes vite-node re-execute this module in place
22
+ // (a plain change would full-reload without disposing), and `dispose` closes the running server first
23
+ // so the next bootstrap() doesn't hit EADDRINUSE on the same port. `import.meta.hot` is undefined in
24
+ // the production SSR build, so this whole block is tree-shaken away there.
25
+ if (import.meta.hot) {
26
+ import.meta.hot.accept()
27
+ import.meta.hot.dispose(async () => {
28
+ await (await app).close()
29
+ })
30
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "lib": ["ES2023"],
5
+ "module": "esnext",
6
+ "moduleResolution": "bundler",
7
+ "moduleDetection": "force",
8
+ "types": ["node", "vite/client"],
9
+
10
+ /* NestJS needs decorators + metadata; param properties and decorators are emitted by vite-plus's
11
+ Oxc transform, so `erasableSyntaxOnly` / `verbatimModuleSyntax` are intentionally left off here. */
12
+ "experimentalDecorators": true,
13
+ "emitDecoratorMetadata": true,
14
+
15
+ "strict": true,
16
+ "strictPropertyInitialization": false,
17
+ "skipLibCheck": true,
18
+ "esModuleInterop": true,
19
+ "allowImportingTsExtensions": true,
20
+ "noEmit": true,
21
+
22
+ /* Linting */
23
+ "noUnusedLocals": true,
24
+ "noUnusedParameters": true,
25
+ "noFallthroughCasesInSwitch": true
26
+ },
27
+ "include": ["src", "vite.config.ts"]
28
+ }
@@ -0,0 +1,35 @@
1
+ import { defineConfig } from 'vite-plus'
2
+
3
+ // NestJS relies on `emitDecoratorMetadata` for its dependency injection. vite-plus's built-in Oxc
4
+ // transform emits it natively (driven by `experimentalDecorators` + `emitDecoratorMetadata` in
5
+ // tsconfig.json), so no extra transform plugin is needed — the api is a plain vite-plus package and
6
+ // `vp check` / `vp test` pick it up with zero wiring.
7
+ export default defineConfig({
8
+ build: {
9
+ // SSR build: bundle the entry to dist/main.js and externalize node_modules (so Nest's optional
10
+ // dynamic requires aren't dragged into the bundle). The workspace contracts package is source
11
+ // TypeScript, so it must be inlined rather than externalized — see `ssr.noExternal` below.
12
+ ssr: 'src/main.ts',
13
+ outDir: 'dist',
14
+ target: 'node22'
15
+ },
16
+ ssr: {
17
+ noExternal: ['@app/contracts']
18
+ },
19
+ lint: {
20
+ plugins: ['typescript', 'oxc'],
21
+ rules: {
22
+ 'vite-plus/prefer-vite-plus-imports': 'error'
23
+ },
24
+ options: {
25
+ typeAware: true,
26
+ typeCheck: true
27
+ },
28
+ jsPlugins: [
29
+ {
30
+ name: 'vite-plus',
31
+ specifier: 'vite-plus/oxlint-plugin'
32
+ }
33
+ ]
34
+ }
35
+ })
@@ -0,0 +1,24 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>web</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@app/web",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vp dev",
8
+ "build": "tsc -b && vp build",
9
+ "lint": "vp lint",
10
+ "preview": "vp preview"
11
+ },
12
+ "dependencies": {
13
+ "@app/contracts": "workspace:*",
14
+ "react": "^19.2.7",
15
+ "react-dom": "^19.2.7"
16
+ },
17
+ "devDependencies": {
18
+ "@rolldown/plugin-babel": "catalog:",
19
+ "@types/node": "^24.13.2",
20
+ "@types/react": "^19.2.17",
21
+ "@types/react-dom": "^19.2.3",
22
+ "@vitejs/plugin-react": "^6.0.2",
23
+ "typescript": "~6.0.2",
24
+ "vite": "catalog:",
25
+ "vite-plus": "catalog:"
26
+ }
27
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
2
+ <rect width="32" height="32" rx="7" fill="#0b0b0b" />
3
+ <path d="M9 10l4.5 12h2L20 10h-2.6l-2.9 8.3L11.6 10z" fill="#e3d000" />
4
+ </svg>
@@ -0,0 +1,48 @@
1
+ import type { Item } from '@app/contracts'
2
+ import { useEffect, useState, type FormEvent } from 'react'
3
+
4
+ // `/api/*` is proxied to the NestJS server in dev (see vite.config.ts), so these are same-origin calls.
5
+ const fetchItems = (): Promise<Item[]> => fetch('/api/items').then((res) => res.json())
6
+
7
+ const createItem = (name: string): Promise<Item> =>
8
+ fetch('/api/items', {
9
+ method: 'POST',
10
+ headers: { 'Content-Type': 'application/json' },
11
+ body: JSON.stringify({ name })
12
+ }).then((res) => res.json())
13
+
14
+ export const App = () => {
15
+ const [items, setItems] = useState<Item[]>([])
16
+ const [name, setName] = useState('')
17
+
18
+ useEffect(() => {
19
+ void fetchItems().then(setItems)
20
+ }, [])
21
+
22
+ const add = async (event: FormEvent) => {
23
+ event.preventDefault()
24
+ if (!name.trim()) return
25
+ const created = await createItem(name.trim())
26
+ setItems((prev) => [...prev, created])
27
+ setName('')
28
+ }
29
+
30
+ return (
31
+ <main style={{ fontFamily: 'system-ui, sans-serif', maxWidth: 480, margin: '4rem auto', padding: '0 1rem' }}>
32
+ <h1>Vite+ · React + NestJS</h1>
33
+ <p>A minimal full-stack starter. The list below is served by the NestJS api.</p>
34
+
35
+ <form onSubmit={add} style={{ display: 'flex', gap: 8, margin: '1.5rem 0' }}>
36
+ <input value={name} onChange={(event) => setName(event.target.value)} placeholder='New item name' style={{ flex: 1, padding: 8 }} />
37
+ <button type='submit'>Add</button>
38
+ </form>
39
+
40
+ <ul>
41
+ {items.map((item) => (
42
+ <li key={item.id}>{item.name}</li>
43
+ ))}
44
+ </ul>
45
+ {items.length === 0 && <p style={{ opacity: 0.6 }}>No items yet — add one above.</p>}
46
+ </main>
47
+ )
48
+ }
@@ -0,0 +1,9 @@
1
+ import { App } from '@app/web/App'
2
+ import { StrictMode } from 'react'
3
+ import { createRoot } from 'react-dom/client'
4
+
5
+ createRoot(document.getElementById('root')!).render(
6
+ <StrictMode>
7
+ <App />
8
+ </StrictMode>
9
+ )
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023", "DOM"],
6
+ "module": "esnext",
7
+ "types": ["vite-plus/client"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+ "jsx": "react-jsx",
17
+
18
+ /* Linting */
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+
24
+ /* Aliases */
25
+ "paths": {
26
+ "@app/web/*": ["./src/*"],
27
+ "@app/contracts": ["../../packages/contracts/src/index.ts"]
28
+ }
29
+ },
30
+ "include": ["src"]
31
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
4
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "es2023",
5
+ "lib": ["ES2023"],
6
+ "types": ["node"],
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "module": "nodenext",
11
+ "allowImportingTsExtensions": true,
12
+ "verbatimModuleSyntax": true,
13
+ "moduleDetection": "force",
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "erasableSyntaxOnly": true,
20
+ "noFallthroughCasesInSwitch": true
21
+ },
22
+ "include": ["vite.config.ts"]
23
+ }
@@ -0,0 +1,45 @@
1
+ import babel from '@rolldown/plugin-babel'
2
+ import react, { reactCompilerPreset } from '@vitejs/plugin-react'
3
+ import { fileURLToPath, URL } from 'url'
4
+ import { defineConfig, lazyPlugins } from 'vite-plus'
5
+
6
+ // https://vite.dev/config/
7
+ export default defineConfig({
8
+ server: {
9
+ port: __WEB_PORT__,
10
+ // Same-origin in dev: requests to `/api/*` are forwarded to the NestJS server, so no CORS and no
11
+ // absolute API URLs in the client code.
12
+ proxy: {
13
+ '/api': 'http://localhost:__API_PORT__'
14
+ }
15
+ },
16
+ lint: {
17
+ plugins: ['react', 'typescript', 'oxc'],
18
+ rules: {
19
+ 'react/rules-of-hooks': 'error',
20
+ 'react/only-export-components': [
21
+ 'warn',
22
+ {
23
+ allowConstantExport: true
24
+ }
25
+ ],
26
+ 'vite-plus/prefer-vite-plus-imports': 'error'
27
+ },
28
+ options: {
29
+ typeAware: true,
30
+ typeCheck: true
31
+ },
32
+ jsPlugins: [
33
+ {
34
+ name: 'vite-plus',
35
+ specifier: 'vite-plus/oxlint-plugin'
36
+ }
37
+ ]
38
+ },
39
+ plugins: lazyPlugins(() => [react(), babel({ presets: [reactCompilerPreset()] })]),
40
+ resolve: {
41
+ alias: {
42
+ '@app/web': fileURLToPath(new URL('./src', import.meta.url))
43
+ }
44
+ }
45
+ })
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vp run -r dev",
8
+ "build": "vp run -r build",
9
+ "check": "vp check",
10
+ "ready": "vp check && vp run -r build"
11
+ },
12
+ "devDependencies": {
13
+ "babel-plugin-react-compiler": "catalog:",
14
+ "vite-plus": "catalog:"
15
+ },
16
+ "devEngines": {
17
+ "packageManager": {
18
+ "name": "pnpm",
19
+ "version": "11.9.0",
20
+ "onFail": "download"
21
+ }
22
+ },
23
+ "engines": {
24
+ "node": ">=22.18.0"
25
+ }
26
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@app/contracts",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "types": "./src/index.ts",
8
+ "exports": {
9
+ ".": "./src/index.ts"
10
+ },
11
+ "scripts": {
12
+ "lint": "vp lint",
13
+ "check": "vp check"
14
+ },
15
+ "dependencies": {
16
+ "zod": "^3.25.76"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "catalog:",
20
+ "typescript": "catalog:",
21
+ "vite": "catalog:",
22
+ "vite-plus": "catalog:"
23
+ }
24
+ }
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod'
2
+
3
+ /**
4
+ * Shared API contracts. Both ends import from here so there is a single source of truth:
5
+ * the api validates request bodies against these schemas (via the ZodValidationPipe) and the
6
+ * web app uses the inferred types to type its `fetch` responses.
7
+ */
8
+
9
+ export const itemSchema = z.object({
10
+ id: z.string(),
11
+ name: z.string().min(1),
12
+ createdAt: z.string()
13
+ })
14
+
15
+ export const createItemSchema = itemSchema.pick({ name: true })
16
+
17
+ export type Item = z.infer<typeof itemSchema>
18
+ export type CreateItem = z.infer<typeof createItemSchema>
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "moduleDetection": "force",
8
+ "types": ["vite-plus/client"],
9
+ "skipLibCheck": true,
10
+ "strict": true,
11
+ "verbatimModuleSyntax": true,
12
+ "allowImportingTsExtensions": true,
13
+ "noEmit": true,
14
+ "noUnusedLocals": true,
15
+ "noUnusedParameters": true
16
+ },
17
+ "include": ["src", "vite.config.ts"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'vite-plus'
2
+
3
+ export default defineConfig({
4
+ lint: {
5
+ plugins: ['typescript', 'oxc'],
6
+ rules: {
7
+ 'vite-plus/prefer-vite-plus-imports': 'error'
8
+ },
9
+ options: {
10
+ typeAware: true,
11
+ typeCheck: true
12
+ },
13
+ jsPlugins: [
14
+ {
15
+ name: 'vite-plus',
16
+ specifier: 'vite-plus/oxlint-plugin'
17
+ }
18
+ ]
19
+ }
20
+ })
@@ -0,0 +1,29 @@
1
+ packages:
2
+ - apps/*
3
+ - packages/*
4
+
5
+ catalogMode: prefer
6
+
7
+ catalog:
8
+ '@types/node': ^24
9
+ typescript: ^5
10
+ vite: npm:@voidzero-dev/vite-plus-core@latest
11
+ vitest: 4.1.9
12
+ vite-plus: ^0.2.1
13
+ babel-plugin-react-compiler: ^1.0.0
14
+ '@rolldown/plugin-babel': ^0.2.3
15
+ overrides:
16
+ vite: 'catalog:'
17
+ vitest: 'catalog:'
18
+ peerDependencyRules:
19
+ allowAny:
20
+ - vite
21
+ - vitest
22
+ allowedVersions:
23
+ vite: '*'
24
+ vitest: '*'
25
+ allowBuilds:
26
+ esbuild: true
27
+ # Telemetry-only postinstall pulled transitively via @nestjs/swagger -> swagger-ui-dist. Declared
28
+ # explicitly (false = do not run) so pnpm doesn't fail with ERR_PNPM_IGNORED_BUILDS.
29
+ '@scarf/scarf': false
@@ -0,0 +1,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "noEmit": true,
4
+ "module": "nodenext",
5
+ "moduleResolution": "nodenext",
6
+ "allowImportingTsExtensions": true,
7
+ "esModuleInterop": true
8
+ }
9
+ }
@@ -0,0 +1,24 @@
1
+ import { defaultExclude, defineConfig } from 'vite-plus'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ // vite-plus's defaultExclude is only node_modules + .git (no dist), so a root-level
6
+ // `vp test` would otherwise scan compiled build output. Keep build outputs out of the scan.
7
+ exclude: [...defaultExclude, '**/dist/**', '**/build/**']
8
+ },
9
+ fmt: {
10
+ jsxSingleQuote: true,
11
+ printWidth: 160,
12
+ semi: false,
13
+ singleQuote: true,
14
+ trailingComma: 'none'
15
+ },
16
+ lint: {
17
+ jsPlugins: [{ name: 'vite-plus', specifier: 'vite-plus/oxlint-plugin' }],
18
+ rules: { 'vite-plus/prefer-vite-plus-imports': 'error' },
19
+ options: { typeAware: true, typeCheck: true }
20
+ },
21
+ run: {
22
+ cache: true
23
+ }
24
+ })