feat: add initial project configuration and smoke tests

- Created pnpm workspace configuration to manage packages.
- Added a placeholder .gitkeep file in the scripts directory.
- Implemented a smoke test script to validate core API and web endpoints.
- Established TypeScript base configuration for consistent compilation settings.
- Introduced Turbo configuration for task management and build processes.
This commit is contained in:
KinSun
2026-03-13 10:30:16 +08:00
commit 9d5616fdc6
68 changed files with 9851 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"webpack": false,
"tsConfigPath": "tsconfig.build.json"
}
}

51
packages/api/package.json Normal file
View File

@@ -0,0 +1,51 @@
{
"name": "@davinci/api",
"version": "0.1.0-alpha.0",
"description": "Davinci Platform — NestJS API backend (Fastify adapter)",
"author": "Galaxis",
"private": true,
"license": "UNLICENSED",
"main": "dist/main.js",
"scripts": {
"build": "nest build",
"start": "node dist/main.js",
"start:dev": "nest start --watch",
"test": "vitest run",
"test:unit": "vitest run --config vitest.config.ts",
"test:e2e": "vitest run --config vitest.e2e.config.ts",
"test:cov": "vitest run --coverage",
"lint": "eslint \"{src,test}/**/*.ts\"",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev"
},
"dependencies": {
"@davinci/shared": "workspace:*",
"@nestjs/common": "^11.0.0",
"@nestjs/config": "^4.0.0",
"@nestjs/core": "^11.0.0",
"@nestjs/platform-fastify": "^11.0.0",
"@nestjs/swagger": "^11.2.6",
"@prisma/adapter-pg": "^7.5.0",
"@prisma/client": "^7.0.0",
"@scalar/fastify-api-reference": "^1.48.5",
"dotenv": "^16.4.0",
"joi": "^17.13.0",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.0"
},
"devDependencies": {
"@nestjs/cli": "^11.0.0",
"@nestjs/testing": "^11.0.0",
"@swc/core": "^1.10.0",
"@types/node": "^22.10.0",
"pino-pretty": "^13.1.3",
"prisma": "^7.0.0",
"tsx": "^4.21.0",
"typescript": "^5.7.0",
"unplugin-swc": "^1.5.0",
"vitest": "^3.0.0"
},
"prisma": {
"seed": "npx tsx prisma/seed.ts"
}
}

View File

@@ -0,0 +1,13 @@
import 'dotenv/config';
import { defineConfig } from 'prisma/config';
export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations',
seed: 'npx tsx prisma/seed.ts',
},
datasource: {
url: process.env['DATABASE_URL'],
},
});

View File

@@ -0,0 +1,14 @@
datasource db {
provider = "postgresql"
}
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
moduleFormat = "cjs"
}
// Placeholder model — removed when real models are added in feature plans
model Placeholder {
id String @id @default(uuid())
}

View File

@@ -0,0 +1,43 @@
/**
* Prisma seed script — creates deterministic test data for local development.
*
* Run: npx prisma db seed
* make dev-seed
*
* This is idempotent — safe to run multiple times.
*
* Replace/extend with real seed data when feature models are added.
*/
import { PrismaClient } from '../src/generated/prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
const adapter = new PrismaPg({ connectionString: process.env['DATABASE_URL']! });
const prisma = new PrismaClient({ adapter });
async function main(): Promise<void> {
console.log('');
console.log('🌱 Seeding davinci-platform dev database...');
console.log('');
// Placeholder — replace when real models exist
const existing = await prisma.placeholder.findFirst();
if (existing) {
console.log(' ⏭ Placeholder record already exists');
} else {
await prisma.placeholder.create({ data: {} });
console.log(' ✅ Placeholder record created');
}
console.log('');
console.log('✅ Seed complete');
console.log('');
}
main()
.catch((e) => {
console.error('❌ Seed failed:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@@ -0,0 +1,23 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
import { HealthModule } from './health/health.module.js';
import { PrismaModule } from './prisma/prisma.module.js';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
DATABASE_URL: Joi.string().uri().required(),
PORT: Joi.number().default(3001),
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
DAVINCI_MODE: Joi.string().valid('mothership', 'community').default('mothership'),
ENABLE_API_DOCS: Joi.string().valid('true', 'false', '1', '0').optional(),
}),
}),
PrismaModule,
HealthModule,
],
})
export class AppModule {}

View File

@@ -0,0 +1,52 @@
import { afterEach, describe, it, expect } from 'vitest';
import { resolveEnableApiDocs, API_VERSIONS } from '../api-docs.setup';
describe('resolveEnableApiDocs', () => {
const originalEnv = process.env.ENABLE_API_DOCS;
afterEach(() => {
if (originalEnv === undefined) {
delete process.env.ENABLE_API_DOCS;
} else {
process.env.ENABLE_API_DOCS = originalEnv;
}
});
it('returns true when ENABLE_API_DOCS=true', () => {
process.env.ENABLE_API_DOCS = 'true';
expect(resolveEnableApiDocs('production')).toBe(true);
});
it('returns true when ENABLE_API_DOCS=1', () => {
process.env.ENABLE_API_DOCS = '1';
expect(resolveEnableApiDocs('production')).toBe(true);
});
it('returns false when ENABLE_API_DOCS=false', () => {
process.env.ENABLE_API_DOCS = 'false';
expect(resolveEnableApiDocs('development')).toBe(false);
});
it('defaults to true in development when not set', () => {
delete process.env.ENABLE_API_DOCS;
expect(resolveEnableApiDocs('development')).toBe(true);
});
it('defaults to false in production when not set', () => {
delete process.env.ENABLE_API_DOCS;
expect(resolveEnableApiDocs('production')).toBe(false);
});
});
describe('API_VERSIONS', () => {
it('has at least one version defined', () => {
expect(API_VERSIONS.length).toBeGreaterThanOrEqual(1);
});
it('has v1 as the default version', () => {
const defaultVersion = API_VERSIONS.find((v) => v.default);
expect(defaultVersion).toBeDefined();
expect(defaultVersion!.version).toBe('1');
expect(defaultVersion!.slug).toBe('v1');
});
});

View File

@@ -0,0 +1,152 @@
import { NestFastifyApplication } from '@nestjs/platform-fastify';
import { ConfigService } from '@nestjs/config';
import {
DocumentBuilder,
OpenAPIObject,
SwaggerModule,
SwaggerDocumentOptions,
} from '@nestjs/swagger';
export interface ApiVersionDefinition {
version: string;
title: string;
slug: string;
default?: boolean;
documentOptions?: SwaggerDocumentOptions;
}
export const API_VERSIONS: ApiVersionDefinition[] = [
{
version: '1',
title: 'API v1',
slug: 'v1',
default: true,
},
];
/**
* Configure multi-version OpenAPI and Scalar API reference UI.
*
* Generates OpenAPI docs via @nestjs/swagger, serves per-version JSON at
* `/api-reference/{slug}/openapi.json`, and registers Scalar UI at `/api-reference`.
*
* The `/api-reference` endpoint is operational (like `/health`) and is NOT
* subject to `/api/v{major}` versioning.
*/
export async function setupApiDocs(
app: NestFastifyApplication,
configService: ConfigService,
): Promise<void> {
const enableDocs = resolveEnableApiDocs(
configService.get<string>('NODE_ENV') || process.env.NODE_ENV || 'development',
);
if (!enableDocs) {
return;
}
const versionDocs = buildVersionedDocuments(app);
registerOpenApiEndpoints(app, versionDocs);
await registerScalarReference(app, versionDocs);
}
/**
* Determine whether API docs should be enabled.
*
* 1. ENABLE_API_DOCS env var (explicit override)
* 2. Default: enabled in development/test, disabled in production
*/
export function resolveEnableApiDocs(nodeEnv: string): boolean {
const explicit = process.env.ENABLE_API_DOCS;
if (explicit !== undefined) {
return explicit === 'true' || explicit === '1';
}
return nodeEnv !== 'production';
}
export function buildVersionedDocuments(app: NestFastifyApplication): Map<string, OpenAPIObject> {
const docs = new Map<string, OpenAPIObject>();
for (const version of API_VERSIONS) {
docs.set(version.slug, buildOpenApiDocument(app, version));
}
return docs;
}
export function buildOpenApiDocument(
app: NestFastifyApplication,
version?: ApiVersionDefinition,
): OpenAPIObject {
const v = version?.version ?? '1';
const config = new DocumentBuilder()
.setTitle('Davinci Platform API')
.setDescription(
'Unified API for the Davinci platform.\n\n' +
'## Versioning\n\n' +
`All business endpoints are served under \`/api/v${v}\`. ` +
'Operational endpoints (`/health`, `/api-reference`) are unversioned.',
)
.setVersion('0.1.0')
.setContact('Davinci', '', '')
.setLicense('UNLICENSED', '')
.addBearerAuth(
{
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description:
'JWT access token. Obtain via POST /api/v1/auth/login or /api/v1/auth/register.',
},
'bearer',
)
.build();
return SwaggerModule.createDocument(app, config, version?.documentOptions);
}
function registerOpenApiEndpoints(
app: NestFastifyApplication,
versionDocs: Map<string, OpenAPIObject>,
): void {
const fastify = app.getHttpAdapter().getInstance();
for (const [slug, doc] of versionDocs) {
fastify.get(
`/api-reference/${slug}/openapi.json`,
{ schema: { hide: true } as Record<string, unknown> },
async () => doc,
);
}
}
async function registerScalarReference(
app: NestFastifyApplication,
versionDocs: Map<string, OpenAPIObject>,
): Promise<void> {
const fastify = app.getHttpAdapter().getInstance();
// Dynamic import — @scalar/fastify-api-reference is ESM-only
const { default: scalarFastifyApiReference } = await import('@scalar/fastify-api-reference');
const sources = API_VERSIONS.filter((v) => versionDocs.has(v.slug)).map((v) => ({
title: v.title,
slug: v.slug,
url: `/api-reference/${v.slug}/openapi.json`,
...(v.default && { default: true }),
}));
await fastify.register(scalarFastifyApiReference, {
routePrefix: '/api-reference',
configuration: {
sources,
theme: 'kepler',
hideClientButton: true,
showDeveloperTools: 'never',
hideDownloadButton: true,
agent: { disabled: true },
} as Record<string, unknown>,
});
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { describe, it, expect, beforeEach } from 'vitest';
import { HealthController } from './health.controller.js';
describe('HealthController', () => {
let controller: HealthController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [HealthController],
}).compile();
controller = module.get<HealthController>(HealthController);
});
it('should return ok status with timestamp', () => {
const result = controller.check();
expect(result.status).toBe('ok');
expect(result.timestamp).toBeDefined();
expect(typeof result.timestamp).toBe('string');
});
});

View File

@@ -0,0 +1,12 @@
import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common';
@Controller({ path: 'health', version: VERSION_NEUTRAL })
export class HealthController {
@Get()
check() {
return {
status: 'ok',
timestamp: new Date().toISOString(),
};
}
}

View File

@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { HealthController } from './health.controller.js';
@Module({
controllers: [HealthController],
})
export class HealthModule {}

57
packages/api/src/main.ts Normal file
View File

@@ -0,0 +1,57 @@
import { NestFactory } from '@nestjs/core';
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
import { VersioningType, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppModule } from './app.module.js';
import { setupApiDocs } from './docs/api-docs.setup.js';
async function bootstrap() {
const logger = new Logger('Bootstrap');
const isDev = process.env.NODE_ENV !== 'production';
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({
logger: {
level: process.env.LOG_LEVEL || (isDev ? 'debug' : 'info'),
...(isDev && {
transport: {
target: 'pino-pretty',
options: { colorize: true, singleLine: true },
},
}),
},
}),
);
// Global prefix — all routes under /api, except operational endpoints
app.setGlobalPrefix('api', {
exclude: ['health', 'health/ready', 'api-reference', 'api-reference/{*path}'],
});
// URI-based versioning: /api/v1/..., /api/v2/...
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: '1',
});
// CORS — env-driven: permissive in dev, strict in production
const corsOrigin = process.env.CORS_ORIGIN;
app.enableCors({
origin: corsOrigin ? corsOrigin.split(',').map((o) => o.trim()) : true,
credentials: true,
});
// Interactive API documentation (Scalar) — conditional on ENABLE_API_DOCS
const configService = app.get(ConfigService);
await setupApiDocs(app, configService);
// Graceful shutdown
app.enableShutdownHooks();
const port = process.env.PORT ?? 3001;
await app.listen(port, '0.0.0.0');
logger.log(`API running on http://localhost:${port}`);
}
bootstrap();

View File

@@ -0,0 +1,33 @@
import { Test, TestingModule } from '@nestjs/testing';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { PrismaService } from '../prisma.service.js';
import { ConfigService } from '@nestjs/config';
describe('PrismaService', () => {
let service: PrismaService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
PrismaService,
{
provide: ConfigService,
useValue: {
getOrThrow: vi.fn().mockReturnValue('postgresql://test:test@localhost:5432/test'),
},
},
],
}).compile();
service = module.get<PrismaService>(PrismaService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should expose PrismaClient methods directly (extends pattern)', () => {
expect(typeof service.$connect).toBe('function');
expect(typeof service.$disconnect).toBe('function');
});
});

View File

@@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service.js';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}

View File

@@ -0,0 +1,33 @@
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaClient } from '../generated/prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
/**
* NestJS-managed Prisma service for Prisma 7.
*
* Follows the official NestJS + Prisma 7 recipe:
* - Uses `prisma-client` generator with `moduleFormat = "cjs"`
* - Extends PrismaClient directly for full type-safe API access
* - Uses PrismaPg driver adapter with connectionString
*
* Usage in feature modules:
* constructor(private readonly prisma: PrismaService) {}
* await this.prisma.placeholder.findMany();
*/
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor(configService: ConfigService) {
const databaseUrl = configService.getOrThrow<string>('DATABASE_URL');
const adapter = new PrismaPg({ connectionString: databaseUrl });
super({ adapter });
}
async onModuleInit(): Promise<void> {
await this.$connect();
}
async onModuleDestroy(): Promise<void> {
await this.$disconnect();
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["dist", "node_modules", "**/*.spec.ts", "**/*.test.ts", "test"]
}

View File

@@ -0,0 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"paths": {
"@davinci/shared": ["../shared/src/index.ts"]
}
},
"include": ["src"],
"exclude": ["dist", "node_modules"]
}

View File

@@ -0,0 +1,18 @@
import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';
import path from 'path';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['src/**/*.spec.ts', 'src/**/*.test.ts'],
exclude: ['src/**/*.e2e-spec.ts'],
},
plugins: [swc.vite()],
resolve: {
alias: {
'@davinci/shared': path.resolve(__dirname, '../shared/src/index.ts'),
},
},
});

View File

@@ -0,0 +1,17 @@
import swc from 'unplugin-swc';
import { defineConfig } from 'vitest/config';
import path from 'path';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['test/**/*.e2e-spec.ts', 'src/**/*.e2e-spec.ts'],
},
plugins: [swc.vite()],
resolve: {
alias: {
'@davinci/shared': path.resolve(__dirname, '../shared/src/index.ts'),
},
},
});

View File

@@ -0,0 +1,20 @@
{
"name": "@davinci/shared",
"version": "0.1.0-alpha.0",
"description": "Davinci Platform — shared types, DTOs, and constants",
"author": "Galaxis",
"private": true,
"license": "UNLICENSED",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run",
"test:unit": "vitest run",
"test:cov": "vitest run --coverage"
},
"devDependencies": {
"typescript": "^5.7.0",
"vitest": "^3.0.0"
}
}

View File

@@ -0,0 +1,9 @@
import { describe, it, expect } from 'vitest';
import { APP_NAME } from '../index.js';
describe('@davinci/shared', () => {
it('exports APP_NAME constant', () => {
expect(APP_NAME).toBeDefined();
expect(APP_NAME).toBe('Davinci Platform');
});
});

View File

@@ -0,0 +1 @@
export const APP_NAME = 'Davinci Platform';

View File

@@ -0,0 +1 @@
export { APP_NAME } from './constants/index.js';

View File

@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"composite": true
},
"include": ["src"],
"exclude": ["dist", "node_modules", "**/*.test.ts"]
}

View File

@@ -0,0 +1,8 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
},
});

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}

View File

@@ -0,0 +1,5 @@
{
"app": {
"title": "Davinci Platform"
}
}

6
packages/web/next-env.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -0,0 +1,10 @@
import createNextIntlPlugin from 'next-intl/plugin';
import type { NextConfig } from 'next';
const withNextIntl = createNextIntlPlugin('./src/i18n/request.ts');
const nextConfig: NextConfig = {
output: 'standalone',
};
export default withNextIntl(nextConfig);

40
packages/web/package.json Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "@davinci/web",
"version": "0.1.0-alpha.0",
"description": "Davinci Platform — Next.js frontend (App Router)",
"author": "Galaxis",
"private": true,
"license": "UNLICENSED",
"scripts": {
"dev": "next dev --port 3000",
"build": "next build",
"start": "next start",
"test": "vitest run",
"test:unit": "vitest run",
"test:cov": "vitest run --coverage",
"lint": "eslint \"src/**/*.{ts,tsx}\""
},
"dependencies": {
"@davinci/shared": "workspace:*",
"clsx": "^2.1.0",
"next": "^15.5.10",
"next-intl": "^4.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^3.0.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.0",
"@testing-library/jest-dom": "^6.6.0",
"@testing-library/react": "^16.1.0",
"@types/node": "^22.10.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.0",
"jsdom": "^28.1.0",
"postcss": "^8.4.0",
"tailwindcss": "^4.0.0",
"typescript": "^5.7.0",
"vitest": "^3.0.0"
}
}

View File

@@ -0,0 +1,7 @@
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};
export default config;

View File

@@ -0,0 +1,17 @@
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
import HomePage from '../app/[locale]/page';
vi.mock('next-intl', () => ({
useTranslations: () => (key: string) => {
const messages: Record<string, string> = { title: 'Davinci Platform' };
return messages[key] ?? key;
},
}));
describe('HomePage', () => {
it('renders the app title', () => {
render(<HomePage />);
expect(screen.getByText('Davinci Platform')).toBeInTheDocument();
});
});

View File

@@ -0,0 +1 @@
import '@testing-library/jest-dom/vitest';

View File

@@ -0,0 +1,27 @@
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { routing } from '@/i18n/routing';
type Props = {
children: React.ReactNode;
params: Promise<{ locale: string }>;
};
export default async function LocaleLayout({ children, params }: Props) {
const { locale } = await params;
if (!routing.locales.includes(locale as (typeof routing.locales)[number])) {
notFound();
}
const messages = await getMessages();
return (
<html lang={locale}>
<body>
<NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>
</body>
</html>
);
}

View File

@@ -0,0 +1,11 @@
import { useTranslations } from 'next-intl';
export default function HomePage() {
const t = useTranslations('app');
return (
<main className="flex min-h-screen flex-col items-center justify-center">
<h1 className="text-4xl font-bold">{t('title')}</h1>
</main>
);
}

View File

@@ -0,0 +1,82 @@
@import "tailwindcss";
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.965 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.965 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.965 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--radius: 0.625rem;
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.145 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.145 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.985 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
}
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@@ -0,0 +1,11 @@
import type { Metadata } from 'next';
import './globals.css';
export const metadata: Metadata = {
title: 'Davinci Platform',
description: 'Davinci Platform — community and mothership',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return children;
}

View File

@@ -0,0 +1,15 @@
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as (typeof routing.locales)[number])) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default,
};
});

View File

@@ -0,0 +1,6 @@
import { defineRouting } from 'next-intl/routing';
export const routing = defineRouting({
locales: ['en'],
defaultLocale: 'en',
});

View File

@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -0,0 +1,8 @@
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
export default createMiddleware(routing);
export const config = {
matcher: ['/', '/(en)/:path*'],
};

View File

@@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "preserve",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"noEmit": true,
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"],
"@davinci/shared": ["../shared/src/index.ts"]
}
},
"include": ["src", "next-env.d.ts", ".next/types/**/*.ts"],
"exclude": ["dist", "node_modules"]
}

View File

@@ -0,0 +1,18 @@
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import path from 'path';
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/__tests__/setup.ts'],
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
'@davinci/shared': path.resolve(__dirname, '../shared/src/index.ts'),
},
},
});