diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 00000000..f1b4d8cc --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,32 @@ +name: Type Check + +on: + push: + branches: + - develop + - main + pull_request: + branches: + - develop + - main + +jobs: + typecheck: + runs-on: ubuntu-latest + env: + THUMBNAIL_URL: ${{ vars.THUMBNAIL_URL }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Type check (project references) + run: bun run typecheck diff --git a/.vscode/settings.json b/.vscode/settings.json index ad6a31bc..76a9c3a4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,5 +16,7 @@ "files.associations": { ".css": "tailwindcss", "*.scss": "tailwindcss" - } + }, + "typescript.tsdk": "./node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true } diff --git a/apps/backend/scripts/build.ts b/apps/backend/scripts/build.ts index 5417739e..2dbb03c2 100644 --- a/apps/backend/scripts/build.ts +++ b/apps/backend/scripts/build.ts @@ -65,14 +65,20 @@ const build = async () => { target: 'bun', minify: false, sourcemap: 'linked', - external: optionalRequirePackages.filter((pkg) => { - try { - require(pkg); - return false; - } catch (_) { - return true; - } - }), + external: [ + ...optionalRequirePackages.filter((pkg) => { + try { + require(pkg); + return false; + } catch (_) { + return true; + } + }), + '@nbw/config', + '@nbw/database', + '@nbw/song', + '@nbw/sounds', + ], splitting: true, }); diff --git a/apps/backend/tsconfig.build.json b/apps/backend/tsconfig.build.json deleted file mode 100644 index 64f86c6b..00000000 --- a/apps/backend/tsconfig.build.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "extends": "./tsconfig.json", - "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] -} diff --git a/apps/backend/tsconfig.json b/apps/backend/tsconfig.json index 7dc80e0f..400170a1 100644 --- a/apps/backend/tsconfig.json +++ b/apps/backend/tsconfig.json @@ -8,20 +8,13 @@ "removeComments": true, "allowSyntheticDefaultImports": true, "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", "emitDecoratorMetadata": true, - // Relaxed strict settings for backend - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false, + // Path mapping + "baseUrl": ".", "paths": { "@server/*": ["src/*"] } }, - "include": ["src/**/*.ts", "src/**/*.d.ts"], - "exclude": ["node_modules", "dist", "e2e/**/*", "test/**/*"] + "include": ["src", "scripts"] } diff --git a/apps/frontend/next.config.mjs b/apps/frontend/next.config.mjs index cdcf10ed..46a3c3fd 100644 --- a/apps/frontend/next.config.mjs +++ b/apps/frontend/next.config.mjs @@ -4,6 +4,8 @@ import createMDX from '@next/mdx'; const nextConfig = { pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'], + // Externalize packages that use Node.js built-in modules for server components + serverExternalPackages: ['@nbw/database', '@nbw/config'], // See: https://github.com/Automattic/node-canvas/issues/867#issuecomment-1925284985 webpack: (config, { isServer }) => { config.externals.push({ @@ -12,8 +14,9 @@ const nextConfig = { // Prevent @nbw/thumbnail from being bundled on the server // It uses HTMLCanvasElement which is not available in Node.js + // Also externalize backend packages that use Node.js modules if (isServer) { - config.externals.push('@nbw/thumbnail'); + config.externals.push('@nbw/thumbnail', '@nbw/database'); } return config; diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 0785ad84..f6186544 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build", + "build": "next build --webpack", "start": "next start", "lint": "eslint \"src/**/*.{ts,tsx}\" --fix", "test": "jest" diff --git a/apps/frontend/src/app/(content)/(info)/blog/[id]/page.tsx b/apps/frontend/src/app/(content)/(info)/blog/[id]/page.tsx index cbc777ba..5773f799 100644 --- a/apps/frontend/src/app/(content)/(info)/blog/[id]/page.tsx +++ b/apps/frontend/src/app/(content)/(info)/blog/[id]/page.tsx @@ -7,7 +7,7 @@ import { PostType, getPostData } from '@web/lib/posts'; import { CustomMarkdown } from '@web/modules/shared/components/CustomMarkdown'; type BlogPageProps = { - params: { id: string }; + params: Promise<{ id: string }>; }; export async function generateMetadata({ diff --git a/apps/frontend/src/app/(content)/(info)/help/[id]/page.tsx b/apps/frontend/src/app/(content)/(info)/help/[id]/page.tsx index 5ace7da7..d776871e 100644 --- a/apps/frontend/src/app/(content)/(info)/help/[id]/page.tsx +++ b/apps/frontend/src/app/(content)/(info)/help/[id]/page.tsx @@ -7,7 +7,7 @@ import { PostType, getPostData } from '@web/lib/posts'; import { CustomMarkdown } from '@web/modules/shared/components/CustomMarkdown'; type HelpPageProps = { - params: { id: string }; + params: Promise<{ id: string }>; }; export async function generateMetadata({ @@ -34,8 +34,8 @@ export async function generateMetadata({ }; } -const HelpPost = ({ params }: HelpPageProps) => { - const { id } = params; +const HelpPost = async ({ params }: HelpPageProps) => { + const { id } = await params; let post: PostType; try { diff --git a/apps/frontend/src/app/(content)/song/[id]/page.tsx b/apps/frontend/src/app/(content)/song/[id]/page.tsx index 44d852c4..75a489d8 100644 --- a/apps/frontend/src/app/(content)/song/[id]/page.tsx +++ b/apps/frontend/src/app/(content)/song/[id]/page.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; import { cookies } from 'next/headers'; -import { SongViewDtoType } from '@nbw/database'; +import type { SongViewDtoType } from '@nbw/database'; import axios from '@web/lib/axios'; import { SongPage } from '@web/modules/song/components/SongPage'; diff --git a/apps/frontend/src/modules/browse/components/SongCard.tsx b/apps/frontend/src/modules/browse/components/SongCard.tsx index 33e62bfe..e2e3d4ac 100644 --- a/apps/frontend/src/modules/browse/components/SongCard.tsx +++ b/apps/frontend/src/modules/browse/components/SongCard.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Link from 'next/link'; import Skeleton from 'react-loading-skeleton'; -import { SongPreviewDtoType } from '@nbw/database'; +import type { SongPreviewDtoType } from '@nbw/database'; import { formatDuration, formatTimeAgo } from '@web/modules/shared/util/format'; import SongThumbnail from '../../shared/components/layout/SongThumbnail'; diff --git a/apps/frontend/src/modules/browse/components/client/context/FeaturedSongs.context.tsx b/apps/frontend/src/modules/browse/components/client/context/FeaturedSongs.context.tsx index c61d116e..f9dda42b 100644 --- a/apps/frontend/src/modules/browse/components/client/context/FeaturedSongs.context.tsx +++ b/apps/frontend/src/modules/browse/components/client/context/FeaturedSongs.context.tsx @@ -4,7 +4,7 @@ import { useEffect } from 'react'; import { create } from 'zustand'; import { TIMESPANS } from '@nbw/config'; -import { type FeaturedSongsDto, type SongPreviewDto } from '@nbw/database'; +import type { FeaturedSongsDto, SongPreviewDto } from '@nbw/database'; type TimespanType = (typeof TIMESPANS)[number]; diff --git a/apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx b/apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx index f16a9346..fa0ec87b 100644 --- a/apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx +++ b/apps/frontend/src/modules/browse/components/client/context/HomePage.context.tsx @@ -1,6 +1,6 @@ 'use client'; -import { FeaturedSongsDtoType, SongPreviewDtoType } from '@nbw/database'; +import type { FeaturedSongsDtoType, SongPreviewDtoType } from '@nbw/database'; import { FeaturedSongsProvider, diff --git a/apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx b/apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx index dfb02318..8e5efd6d 100644 --- a/apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx +++ b/apps/frontend/src/modules/browse/components/client/context/RecentSongs.context.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react'; import { create } from 'zustand'; -import { PageDto, SongPreviewDtoType } from '@nbw/database'; +import type { PageDto, SongPreviewDtoType } from '@nbw/database'; import axiosInstance from '@web/lib/axios'; interface RecentSongsState { diff --git a/apps/frontend/src/modules/my-songs/components/client/SongRow.tsx b/apps/frontend/src/modules/my-songs/components/client/SongRow.tsx index a875b484..418e8f38 100644 --- a/apps/frontend/src/modules/my-songs/components/client/SongRow.tsx +++ b/apps/frontend/src/modules/my-songs/components/client/SongRow.tsx @@ -8,7 +8,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Link from 'next/link'; import Skeleton from 'react-loading-skeleton'; -import { SongPreviewDtoType } from '@nbw/database'; +import type { SongPreviewDtoType } from '@nbw/database'; import SongThumbnail from '@web/modules/shared/components/layout/SongThumbnail'; import { formatDuration } from '@web/modules/shared/util/format'; diff --git a/apps/frontend/src/modules/shared/components/layout/RandomSongButton.tsx b/apps/frontend/src/modules/shared/components/layout/RandomSongButton.tsx index 6eeff915..9d82e188 100644 --- a/apps/frontend/src/modules/shared/components/layout/RandomSongButton.tsx +++ b/apps/frontend/src/modules/shared/components/layout/RandomSongButton.tsx @@ -4,7 +4,7 @@ import { faDice } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { useRouter } from 'next/navigation'; -import { PageDto, SongPreviewDto } from '@nbw/database'; +import type { PageDto, SongPreviewDto } from '@nbw/database'; import axios from '@web/lib/axios'; import { MusicalNote } from './MusicalNote'; diff --git a/apps/frontend/src/modules/song-edit/components/client/EditSongPage.tsx b/apps/frontend/src/modules/song-edit/components/client/EditSongPage.tsx index 36be5f6a..e6da180d 100644 --- a/apps/frontend/src/modules/song-edit/components/client/EditSongPage.tsx +++ b/apps/frontend/src/modules/song-edit/components/client/EditSongPage.tsx @@ -1,4 +1,4 @@ -import { UploadSongDtoType } from '@nbw/database'; +import type { UploadSongDtoType } from '@nbw/database'; import axiosInstance from '@web/lib/axios'; import { getTokenServer, diff --git a/apps/frontend/src/modules/song/components/client/DownloadSongModal.tsx b/apps/frontend/src/modules/song/components/client/DownloadSongModal.tsx index b6095ef4..b1b75efa 100644 --- a/apps/frontend/src/modules/song/components/client/DownloadSongModal.tsx +++ b/apps/frontend/src/modules/song/components/client/DownloadSongModal.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; -import { SongViewDtoType } from '@nbw/database'; +import type { SongViewDtoType } from '@nbw/database'; import { DownloadPopupAdSlot } from '@web/modules/shared/components/client/ads/AdSlots'; import GenericModal from '@web/modules/shared/components/client/GenericModal'; diff --git a/apps/frontend/src/modules/song/components/client/SongCanvas.tsx b/apps/frontend/src/modules/song/components/client/SongCanvas.tsx index 0deb67a0..07e34e6d 100644 --- a/apps/frontend/src/modules/song/components/client/SongCanvas.tsx +++ b/apps/frontend/src/modules/song/components/client/SongCanvas.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'; -import { SongViewDtoType } from '@nbw/database'; +import type { SongViewDtoType } from '@nbw/database'; import axios from '@web/lib/axios'; export const SongCanvas = ({ song }: { song: SongViewDtoType }) => { diff --git a/apps/frontend/src/modules/song/components/client/SongThumbnailInput.tsx b/apps/frontend/src/modules/song/components/client/SongThumbnailInput.tsx index 27bdab49..40acb09b 100644 --- a/apps/frontend/src/modules/song/components/client/SongThumbnailInput.tsx +++ b/apps/frontend/src/modules/song/components/client/SongThumbnailInput.tsx @@ -11,7 +11,7 @@ import { import { Slider } from '@web/modules/shared/components/ui/slider'; import { useSongProvider } from './context/Song.context'; -import { EditSongForm, UploadSongForm } from './SongForm.zod'; +import { EditSongFormInput, UploadSongFormInput } from './SongForm.zod'; import { ThumbnailRendererCanvas } from './ThumbnailRenderer'; const formatZoomLevel = (zoomLevel: number) => { @@ -20,7 +20,8 @@ const formatZoomLevel = (zoomLevel: number) => { }; type ThumbnailSlidersProps = { - formMethods: UseFormReturn & UseFormReturn; + formMethods: UseFormReturn & + UseFormReturn; isLocked: boolean; maxTick: number; maxLayer: number; @@ -48,7 +49,7 @@ const ThumbnailSliders: React.FC = ({
{ setValue('thumbnailData.zoomLevel', value[0], { shouldValidate: true, @@ -60,14 +61,16 @@ const ThumbnailSliders: React.FC = ({ max={THUMBNAIL_CONSTANTS.zoomLevel.max} />
-
{formatZoomLevel(zoomLevel)}
+
+ {formatZoomLevel(zoomLevel ?? THUMBNAIL_CONSTANTS.zoomLevel.default)} +
{ setValue('thumbnailData.startTick', value[0], { shouldValidate: true, @@ -86,7 +89,7 @@ const ThumbnailSliders: React.FC = ({
{ setValue('thumbnailData.startLayer', value[0], { shouldValidate: true, diff --git a/apps/frontend/src/modules/song/components/client/ThumbnailRenderer.tsx b/apps/frontend/src/modules/song/components/client/ThumbnailRenderer.tsx index 6d11a28b..6cd0da89 100644 --- a/apps/frontend/src/modules/song/components/client/ThumbnailRenderer.tsx +++ b/apps/frontend/src/modules/song/components/client/ThumbnailRenderer.tsx @@ -3,14 +3,15 @@ import { useEffect, useRef, useState } from 'react'; import { UseFormReturn } from 'react-hook-form'; +import { THUMBNAIL_CONSTANTS } from '@nbw/config'; import { NoteQuadTree } from '@nbw/song'; import { drawNotesOffscreen, swap } from '@nbw/thumbnail/browser'; -import { UploadSongForm } from './SongForm.zod'; +import { UploadSongFormInput } from './SongForm.zod'; type ThumbnailRendererCanvasProps = { notes: NoteQuadTree; - formMethods: UseFormReturn; + formMethods: UseFormReturn; }; export const ThumbnailRendererCanvas = ({ @@ -60,10 +61,11 @@ export const ThumbnailRendererCanvas = ({ try { const output = (await drawNotesOffscreen({ notes, - startTick, - startLayer, - zoomLevel, - backgroundColor, + startTick: startTick ?? THUMBNAIL_CONSTANTS.startTick.default, + startLayer: startLayer ?? THUMBNAIL_CONSTANTS.startLayer.default, + zoomLevel: zoomLevel ?? THUMBNAIL_CONSTANTS.zoomLevel.default, + backgroundColor: + backgroundColor ?? THUMBNAIL_CONSTANTS.backgroundColor.default, canvasWidth: canvas.width, imgWidth: 1280, imgHeight: 768, diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json index 515247c9..0b662962 100644 --- a/apps/frontend/tsconfig.json +++ b/apps/frontend/tsconfig.json @@ -13,12 +13,11 @@ "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, + // Override base strict settings for Next.js compatibility "strict": true, - "strictNullChecks": true, - "noImplicitAny": true, - "strictBindCallApply": true, "strictPropertyInitialization": false, + // Next.js plugins and types "plugins": [ { @@ -26,9 +25,11 @@ } ], "types": ["mdx"], + // Path mapping + "baseUrl": ".", "paths": { - "@web/*": ["./src/*"] + "@web/*": ["src/*"] } }, "include": [ @@ -37,10 +38,5 @@ "**/*.tsx", "**/*.mdx", ".next/types/**/*.ts" - ], - "types": ["mdx"], - // Path mapping - "paths": { - "@web/*": ["./src/*"] - } + ] } diff --git a/bun.lock b/bun.lock index a7ed6734..b2cdbafe 100644 --- a/bun.lock +++ b/bun.lock @@ -17,6 +17,7 @@ "concurrently": "^9.2.1", "eslint": "^9.36.0", "eslint-config-prettier": "^8.10.2", + "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-mdx": "^3.6.2", "eslint-plugin-unused-imports": "^4.2.0", @@ -1668,9 +1669,11 @@ "eslint-config-prettier": ["eslint-config-prettier@8.10.2", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A=="], + "eslint-import-context": ["eslint-import-context@0.1.9", "", { "dependencies": { "get-tsconfig": "^4.10.1", "stable-hash-x": "^0.2.0" }, "peerDependencies": { "unrs-resolver": "^1.0.0" }, "optionalPeers": ["unrs-resolver"] }, "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg=="], + "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="], - "eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="], + "eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@4.4.4", "", { "dependencies": { "debug": "^4.4.1", "eslint-import-context": "^0.1.8", "get-tsconfig": "^4.10.1", "is-bun-module": "^2.0.0", "stable-hash-x": "^0.2.0", "tinyglobby": "^0.2.14", "unrs-resolver": "^1.7.11" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw=="], "eslint-mdx": ["eslint-mdx@3.6.2", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "espree": "^9.6.1 || ^10.4.0", "estree-util-visit": "^2.0.0", "remark-mdx": "^3.1.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "synckit": "^0.11.8", "unified": "^11.0.5", "unified-engine": "^11.2.2", "unist-util-visit": "^5.0.0", "uvu": "^0.5.6", "vfile": "^6.0.3" }, "peerDependencies": { "eslint": ">=8.0.0", "remark-lint-file-extension": "*" }, "optionalPeers": ["remark-lint-file-extension"] }, "sha512-5hczn5iSSEcwtNtVXFwCKIk6iLEDaZpwc3vjYDl/B779OzaAAK/ou16J2xVdO6ecOLEO1WZqp7MRCQ/WsKDUig=="], @@ -2626,7 +2629,7 @@ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], @@ -2898,6 +2901,8 @@ "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="], + "stable-hash-x": ["stable-hash-x@0.2.0", "", {}, "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ=="], + "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], @@ -3424,6 +3429,8 @@ "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], + "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], "bl/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], @@ -3456,6 +3463,8 @@ "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + "eslint-config-next/eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.10.1", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.4.0", "get-tsconfig": "^4.10.0", "is-bun-module": "^2.0.0", "stable-hash": "^0.0.5", "tinyglobby": "^0.2.13", "unrs-resolver": "^1.6.2" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ=="], + "eslint-config-next/globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -3474,8 +3483,6 @@ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "fdir/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], "fixpack/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], @@ -3540,8 +3547,6 @@ "jest-snapshot/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "jest-util/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "jest-watcher/@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], "js-beautify/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], @@ -3568,6 +3573,8 @@ "make-dir/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "mjml-cli/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "mjml-cli/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="], @@ -3968,8 +3975,6 @@ "test-exclude/glob": ["glob@7.1.7", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ=="], - "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - "ts-jest/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "ts-loader/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -4066,6 +4071,8 @@ "@jest/transform/jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + "@jest/transform/jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@nbw/backend/@types/bun/bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], "@nbw/backend/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], @@ -4188,6 +4195,8 @@ "create-jest/jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + "create-jest/jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "editorconfig/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "eslint-plugin-import/tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], @@ -4528,6 +4537,8 @@ "log-update/cli-cursor/restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + "mjml-cli/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "mjml-cli/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "multer/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -4642,6 +4653,8 @@ "@nbw/thumbnail/jest/@jest/core/jest-snapshot/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@nbw/thumbnail/jest/@jest/core/jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@nbw/thumbnail/jest/@jest/core/pretty-format/@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="], "@nbw/thumbnail/jest/@jest/core/pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -4672,6 +4685,8 @@ "@nbw/thumbnail/jest/jest-cli/jest-util/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], + "@nbw/thumbnail/jest/jest-cli/jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "@nbw/thumbnail/jest/jest-cli/jest-validate/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], "@nestjs/mongoose/mongoose/mongodb/mongodb-connection-string-url/@types/whatwg-url": ["@types/whatwg-url@8.2.2", "", { "dependencies": { "@types/node": "*", "@types/webidl-conversions": "*" } }, "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA=="], diff --git a/eslint.config.mjs b/eslint.config.mjs index 688d6684..dedf562d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -31,6 +31,13 @@ export default defineConfig( { files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'], languageOptions: { + parserOptions: { + project: [ + './tsconfig.base.json', + './packages/*/tsconfig.json', + './apps/*/tsconfig.json', + ], + }, globals: { ...globals.node, ...globals.es2021, ...globals.bun }, }, plugins: { @@ -40,11 +47,7 @@ export default defineConfig( settings: { 'import/resolver': { typescript: { - project: [ - 'apps/*/tsconfig.json', - 'packages/*/tsconfig.json', - './tsconfig.json', - ], + project: './tsconfig.eslint.json', }, node: true, }, diff --git a/package.json b/package.json index ec99f8c3..6d8e442f 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "dev:web": "bun run --filter '@nbw/frontend' dev", "dev:apps": "bun run --filter './apps/*' dev", "dev": "concurrently --names 'backend,frontend' --prefix-colors 'cyan,magenta' 'cd apps/backend && bun run start:dev' 'cd apps/frontend && bun run dev'", + "typecheck": "tsc -b --noEmit", "lint": "eslint \"**/*.{ts,tsx}\" --fix", "lint:check": "eslint \"**/*.{ts,tsx}\"", "format": "prettier --write .", @@ -81,6 +82,7 @@ "concurrently": "^9.2.1", "eslint": "^9.36.0", "eslint-config-prettier": "^8.10.2", + "eslint-import-resolver-typescript": "^4.4.4", "eslint-plugin-import": "^2.32.0", "eslint-plugin-mdx": "^3.6.2", "eslint-plugin-unused-imports": "^4.2.0", diff --git a/packages/configs/package.json b/packages/configs/package.json index b641eb87..26f7f0b5 100644 --- a/packages/configs/package.json +++ b/packages/configs/package.json @@ -13,8 +13,7 @@ } }, "scripts": { - "build": "bun run clean && bun build src/index.ts --outdir dist --target node && bun run build:types", - "build:types": "tsc --project tsconfig.build.json", + "build": "bun run scripts/build.ts", "clean": "rm -rf dist", "dev": "bun build src/index.ts --outdir dist --target node --watch", "lint": "eslint \"src/**/*.ts\" --fix", diff --git a/packages/configs/scripts/build.ts b/packages/configs/scripts/build.ts new file mode 100644 index 00000000..b0444cc1 --- /dev/null +++ b/packages/configs/scripts/build.ts @@ -0,0 +1,87 @@ +import { $ } from 'bun'; +import { existsSync, readdirSync, renameSync, rmSync } from 'fs'; +import { join } from 'path'; + +// When running via "bun run scripts/build.ts", process.cwd() is the package root +const packageRoot = process.cwd(); + +async function clean() { + const distPath = join(packageRoot, 'dist'); + if (existsSync(distPath)) { + rmSync(distPath, { recursive: true, force: true }); + } +} + +async function buildWithBun() { + const result = await Bun.build({ + entrypoints: [join(packageRoot, 'src', 'index.ts')], + outdir: join(packageRoot, 'dist'), + target: 'node', + }); + + if (!result.success) { + console.error('Bun build failed:', result.logs); + process.exit(1); + } + + console.log('Bun build completed successfully'); +} + +async function buildTypes() { + // Change to package root directory for tsc command + const result = await $`cd ${packageRoot} && tsc -b`.quiet(); + + if (result.exitCode !== 0) { + console.error('TypeScript build failed'); + process.exit(1); + } + + console.log('TypeScript declaration files generated'); +} + +function fixDeclarationFiles() { + const distSrcPath = join(packageRoot, 'dist', 'src'); + const distPath = join(packageRoot, 'dist'); + + if (existsSync(distSrcPath)) { + const files = readdirSync(distSrcPath); + + // Move all .d.ts files from dist/src to dist + for (const file of files) { + if (file.endsWith('.d.ts')) { + const srcFile = join(distSrcPath, file); + const destFile = join(distPath, file); + renameSync(srcFile, destFile); + console.log(`Moved ${file} to dist/`); + } + } + + // Remove the now-empty dist/src directory + try { + rmSync(distSrcPath, { recursive: true, force: true }); + } catch (error) { + // Ignore errors if directory is not empty or doesn't exist + } + } +} + +async function build() { + console.log('Cleaning dist directory...'); + await clean(); + + console.log('Building with Bun...'); + await buildWithBun(); + + console.log('Building TypeScript declaration files...'); + await buildTypes(); + + console.log('Fixing declaration file locations...'); + fixDeclarationFiles(); + + console.log('Build completed successfully!'); +} + +build().catch((error) => { + console.error('Build failed:', error); + process.exit(1); +}); diff --git a/packages/configs/tsconfig.build.json b/packages/configs/tsconfig.build.json deleted file mode 100644 index 22eb2159..00000000 --- a/packages/configs/tsconfig.build.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": false, - "declaration": true, - "outDir": "dist", - "allowImportingTsExtensions": false, - "verbatimModuleSyntax": false, - "module": "esnext", - "moduleResolution": "bundler" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] -} diff --git a/packages/configs/tsconfig.json b/packages/configs/tsconfig.json index 069a1dc8..5273baea 100644 --- a/packages/configs/tsconfig.json +++ b/packages/configs/tsconfig.json @@ -1,8 +1,9 @@ { "extends": "../../tsconfig.package.json", "compilerOptions": { - // Package-specific overrides (if any) - "module": "esnext", - "moduleResolution": "bundler" - } + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src/**/*.ts"] } diff --git a/packages/database/package.json b/packages/database/package.json index 6b09960d..61e5d06f 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -17,8 +17,8 @@ }, "scripts": { "build": "bun run clean && bun run build:js && bun run build:types", - "build:js": "tsc --project tsconfig.build.json --declaration false --emitDeclarationOnly false", - "build:types": "tsc --project tsconfig.build.json --emitDeclarationOnly", + "build:js": "tsc --project tsconfig.build.json", + "build:types": "tsc --project tsconfig.types.json", "clean": "rm -rf dist", "dev": "tsc --project tsconfig.build.json --watch", "lint": "eslint \"src/**/*.ts\" --fix", diff --git a/packages/database/src/common/dto/PageQuery.dto.ts b/packages/database/src/common/dto/PageQuery.dto.ts index 6c508b1e..a0f0025c 100644 --- a/packages/database/src/common/dto/PageQuery.dto.ts +++ b/packages/database/src/common/dto/PageQuery.dto.ts @@ -11,9 +11,10 @@ import { Min, } from 'class-validator'; -import type { TimespanType } from '@database/song/dto/types'; import { TIMESPANS } from '@nbw/config'; +import type { TimespanType } from '../../song/dto/types'; + export class PageQueryDTO { @Min(1) @ApiProperty({ diff --git a/packages/database/src/song/dto/SongPreview.dto.ts b/packages/database/src/song/dto/SongPreview.dto.ts index 97acff3c..38ce760a 100644 --- a/packages/database/src/song/dto/SongPreview.dto.ts +++ b/packages/database/src/song/dto/SongPreview.dto.ts @@ -1,6 +1,8 @@ import { IsNotEmpty, IsString, IsUrl, MaxLength } from 'class-validator'; -import type { SongWithUser } from '@database/song/entity/song.entity'; +import type { SongWithUser } from '../../song/entity/song.entity'; + +import type { VisibilityType } from './types'; type SongPreviewUploader = { username: string; @@ -50,7 +52,7 @@ export class SongPreviewDto { @IsNotEmpty() @IsString() - visibility: string; + visibility: VisibilityType; constructor(partial: Partial) { Object.assign(this, partial); diff --git a/packages/database/src/song/dto/SongView.dto.ts b/packages/database/src/song/dto/SongView.dto.ts index b7b5ed6f..58d07c04 100644 --- a/packages/database/src/song/dto/SongView.dto.ts +++ b/packages/database/src/song/dto/SongView.dto.ts @@ -7,8 +7,8 @@ import { IsUrl, } from 'class-validator'; -import { SongStats } from '@database/song/dto/SongStats'; -import type { SongDocument } from '@database/song/entity/song.entity'; +import { SongStats } from '../../song/dto/SongStats'; +import type { SongDocument } from '../../song/entity/song.entity'; import type { CategoryType, LicenseType, VisibilityType } from './types'; diff --git a/packages/database/src/song/dto/UploadSongDto.dto.ts b/packages/database/src/song/dto/UploadSongDto.dto.ts index 5e9e3e08..8e973050 100644 --- a/packages/database/src/song/dto/UploadSongDto.dto.ts +++ b/packages/database/src/song/dto/UploadSongDto.dto.ts @@ -10,9 +10,10 @@ import { ValidateNested, } from 'class-validator'; -import type { SongDocument } from '@database/song/entity/song.entity'; import { UPLOAD_CONSTANTS } from '@nbw/config'; +import type { SongDocument } from '../../song/entity/song.entity'; + import { ThumbnailData } from './ThumbnailData.dto'; import type { CategoryType, LicenseType, VisibilityType } from './types'; diff --git a/packages/database/src/song/dto/UploadSongResponseDto.dto.ts b/packages/database/src/song/dto/UploadSongResponseDto.dto.ts index 2fedb3e1..b83acb2b 100644 --- a/packages/database/src/song/dto/UploadSongResponseDto.dto.ts +++ b/packages/database/src/song/dto/UploadSongResponseDto.dto.ts @@ -7,7 +7,7 @@ import { ValidateNested, } from 'class-validator'; -import type { SongWithUser } from '@database/song/entity/song.entity'; +import type { SongWithUser } from '../../song/entity/song.entity'; import * as SongViewDto from './SongView.dto'; import { ThumbnailData } from './ThumbnailData.dto'; diff --git a/packages/database/tsconfig.build.json b/packages/database/tsconfig.build.json index 15e1f5c2..0fc0958a 100644 --- a/packages/database/tsconfig.build.json +++ b/packages/database/tsconfig.build.json @@ -1,16 +1,21 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "emitDecoratorMetadata": true, - "noEmit": false, - "declaration": true, - "outDir": "dist", - "allowImportingTsExtensions": false, - "verbatimModuleSyntax": false, + // Unlike other packages where we use a bundler to output JS, + // this package uses tsc for its build step. + // We must disable the default 'composite' config to output JS. + "composite": false, + "declaration": false, + "emitDeclarationOnly": false, + + // Module target for Node runtime + "module": "CommonJS", "moduleResolution": "node", - "module": "ESNext", - "typeRoots": ["./node_modules/@types"] + "target": "ES2021", + + // Allow ES imports + "verbatimModuleSyntax": false }, "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] + "exclude": ["**/*.spec.ts", "**/*.test.ts"] } diff --git a/packages/database/tsconfig.json b/packages/database/tsconfig.json index 1a7a926f..9547fe19 100644 --- a/packages/database/tsconfig.json +++ b/packages/database/tsconfig.json @@ -1,22 +1,12 @@ { "extends": "../../tsconfig.package.json", "compilerOptions": { - // Database-specific settings - "declaration": true, - "removeComments": true, - "allowSyntheticDefaultImports": true, - "sourceMap": true, - "outDir": "./dist", - "baseUrl": "./", - "emitDecoratorMetadata": true, - // Relaxed strict settings for database entities - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - // Path mapping - "paths": { - "@database/*": ["src/*"] - } - } + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo", + + // Database runtime requirements + "emitDecoratorMetadata": true + }, + "include": ["src/**/*.ts"] } diff --git a/packages/database/tsconfig.types.json b/packages/database/tsconfig.types.json new file mode 100644 index 00000000..be175bda --- /dev/null +++ b/packages/database/tsconfig.types.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + // Emit-only configuration for building types. + "declaration": true, + "emitDeclarationOnly": true + } +} diff --git a/packages/song/package.json b/packages/song/package.json index 0f0003d3..612ae51e 100644 --- a/packages/song/package.json +++ b/packages/song/package.json @@ -15,10 +15,9 @@ } }, "scripts": { - "build": "bun run clean && bun run build:node && bun run build:browser && bun run build:types", + "build": "bun run scripts/build.ts", "build:node": "bun build src/index.ts --outdir dist --target node && mv dist/index.js dist/index.node.js", "build:browser": "bun build src/index.ts --outdir dist --target browser && mv dist/index.js dist/index.browser.js", - "build:types": "tsc --project tsconfig.build.json", "clean": "rm -rf dist", "dev": "bun build src/index.ts --outdir dist --target node --watch", "lint": "eslint \"src/**/*.ts\" --fix", @@ -31,7 +30,6 @@ }, "dependencies": { "@encode42/nbs.js": "^5.0.2", - "@nbw/database": "workspace:*", "@timohausmann/quadtree-ts": "^2.2.2", "jszip": "^3.10.1", "unidecode": "^1.1.0" diff --git a/packages/song/scripts/build.ts b/packages/song/scripts/build.ts new file mode 100644 index 00000000..c3eb47a9 --- /dev/null +++ b/packages/song/scripts/build.ts @@ -0,0 +1,119 @@ +import { $ } from 'bun'; +import { existsSync, readdirSync, renameSync, rmSync } from 'fs'; +import { join } from 'path'; + +// When running via "bun run scripts/build.ts", process.cwd() is the package root +const packageRoot = process.cwd(); + +async function clean() { + const distPath = join(packageRoot, 'dist'); + if (existsSync(distPath)) { + rmSync(distPath, { recursive: true, force: true }); + } +} + +async function buildNode() { + const result = await Bun.build({ + entrypoints: [join(packageRoot, 'src', 'index.ts')], + outdir: join(packageRoot, 'dist'), + target: 'node', + }); + + if (!result.success) { + console.error('Bun build (node) failed:', result.logs); + process.exit(1); + } + + // Rename index.js to index.node.js + const indexJs = join(packageRoot, 'dist', 'index.js'); + const indexNodeJs = join(packageRoot, 'dist', 'index.node.js'); + if (existsSync(indexJs)) { + renameSync(indexJs, indexNodeJs); + } + + console.log('Bun build (node) completed successfully'); +} + +async function buildBrowser() { + const result = await Bun.build({ + entrypoints: [join(packageRoot, 'src', 'index.ts')], + outdir: join(packageRoot, 'dist'), + target: 'browser', + }); + + if (!result.success) { + console.error('Bun build (browser) failed:', result.logs); + process.exit(1); + } + + // Rename index.js to index.browser.js + const indexJs = join(packageRoot, 'dist', 'index.js'); + const indexBrowserJs = join(packageRoot, 'dist', 'index.browser.js'); + if (existsSync(indexJs)) { + renameSync(indexJs, indexBrowserJs); + } + + console.log('Bun build (browser) completed successfully'); +} + +async function buildTypes() { + // Change to package root directory for tsc command + const result = await $`cd ${packageRoot} && tsc -b`.quiet(); + + if (result.exitCode !== 0) { + console.error('TypeScript build failed'); + process.exit(1); + } + + console.log('TypeScript declaration files generated'); +} + +function fixDeclarationFiles() { + const distSrcPath = join(packageRoot, 'dist', 'src'); + const distPath = join(packageRoot, 'dist'); + + if (existsSync(distSrcPath)) { + const files = readdirSync(distSrcPath); + + // Move all .d.ts files from dist/src to dist + for (const file of files) { + if (file.endsWith('.d.ts')) { + const srcFile = join(distSrcPath, file); + const destFile = join(distPath, file); + renameSync(srcFile, destFile); + console.log(`Moved ${file} to dist/`); + } + } + + // Remove the now-empty dist/src directory + try { + rmSync(distSrcPath, { recursive: true, force: true }); + } catch (error) { + // Ignore errors if directory is not empty or doesn't exist + } + } +} + +async function build() { + console.log('Cleaning dist directory...'); + await clean(); + + console.log('Building with Bun (node target)...'); + await buildNode(); + + console.log('Building with Bun (browser target)...'); + await buildBrowser(); + + console.log('Building TypeScript declaration files...'); + await buildTypes(); + + console.log('Fixing declaration file locations...'); + fixDeclarationFiles(); + + console.log('Build completed successfully!'); +} + +build().catch((error) => { + console.error('Build failed:', error); + process.exit(1); +}); diff --git a/packages/song/src/types.ts b/packages/song/src/types.ts index 1de71613..19dc4bbe 100644 --- a/packages/song/src/types.ts +++ b/packages/song/src/types.ts @@ -1,5 +1,3 @@ -import { SongStats } from '@nbw/database'; - import { NoteQuadTree } from './notes'; export type SongFileType = { @@ -30,4 +28,27 @@ export type Instrument = { count: number; }; -export type SongStatsType = InstanceType; +// Type definition matching SongStats from @nbw/database +// Defined here to avoid pulling in backend dependencies +export type SongStatsType = { + midiFileName: string; + noteCount: number; + tickCount: number; + layerCount: number; + tempo: number; + tempoRange: number[] | null; + timeSignature: number; + duration: number; + loop: boolean; + loopStartTick: number; + minutesSpent: number; + vanillaInstrumentCount: number; + customInstrumentCount: number; + firstCustomInstrumentIndex: number; + outOfRangeNoteCount: number; + detunedNoteCount: number; + customInstrumentNoteCount: number; + incompatibleNoteCount: number; + compatible: boolean; + instrumentNoteCounts: number[]; +}; diff --git a/packages/song/tsconfig.build.json b/packages/song/tsconfig.build.json deleted file mode 100644 index 0fc16b66..00000000 --- a/packages/song/tsconfig.build.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": false, - "emitDeclarationOnly": true, - "declaration": true, - "outDir": "dist" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] -} diff --git a/packages/song/tsconfig.json b/packages/song/tsconfig.json index 8ac70592..5273baea 100644 --- a/packages/song/tsconfig.json +++ b/packages/song/tsconfig.json @@ -1,3 +1,9 @@ { - "extends": "../../tsconfig.package.json" + "extends": "../../tsconfig.package.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src/**/*.ts"] } diff --git a/packages/sounds/package.json b/packages/sounds/package.json index 88bd226f..70abe826 100644 --- a/packages/sounds/package.json +++ b/packages/sounds/package.json @@ -13,8 +13,7 @@ } }, "scripts": { - "build": "bun run clean && bun build src/index.ts --outdir dist --target node && bun run build:types", - "build:types": "tsc --project tsconfig.build.json", + "build": "bun run clean && bun build src/index.ts --outdir dist --target node && tsc -b", "clean": "rm -rf dist", "dev": "bun build src/index.ts --outdir dist --target node --watch", "lint": "eslint \"src/**/*.ts\" --fix", diff --git a/packages/sounds/tsconfig.build.json b/packages/sounds/tsconfig.build.json deleted file mode 100644 index 0fc16b66..00000000 --- a/packages/sounds/tsconfig.build.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": false, - "emitDeclarationOnly": true, - "declaration": true, - "outDir": "dist" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] -} diff --git a/packages/sounds/tsconfig.json b/packages/sounds/tsconfig.json index 92f024c9..5273baea 100644 --- a/packages/sounds/tsconfig.json +++ b/packages/sounds/tsconfig.json @@ -1,6 +1,9 @@ { "extends": "../../tsconfig.package.json", "compilerOptions": { - "baseUrl": "./src" - } + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src/**/*.ts"] } diff --git a/packages/thumbnail/package.json b/packages/thumbnail/package.json index 4f4b95d9..cd4b7ded 100644 --- a/packages/thumbnail/package.json +++ b/packages/thumbnail/package.json @@ -14,10 +14,9 @@ } }, "scripts": { - "build": "bun run clean && bun run build:browser && bun run build:node && bun run build:types", + "build": "bun run clean && bun run build:browser && bun run build:node && tsc -b", "build:browser": "bun build src/browser/index.ts --outdir dist/browser --target browser --external @napi-rs/canvas", "build:node": "bun build src/node/index.ts --outdir dist/node --target node", - "build:types": "tsc --project tsconfig.build.json", "clean": "rm -rf dist", "dev:browser": "bun build src/browser/index.ts --outdir dist/browser --target node --watch", "dev:node": "bun build src/node/index.ts --outdir dist/node --target node --watch", diff --git a/packages/thumbnail/tsconfig.build.json b/packages/thumbnail/tsconfig.build.json deleted file mode 100644 index 0fc16b66..00000000 --- a/packages/thumbnail/tsconfig.build.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": false, - "emitDeclarationOnly": true, - "declaration": true, - "outDir": "dist" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] -} diff --git a/packages/thumbnail/tsconfig.json b/packages/thumbnail/tsconfig.json index 168733fc..be7e6f6d 100644 --- a/packages/thumbnail/tsconfig.json +++ b/packages/thumbnail/tsconfig.json @@ -1,7 +1,12 @@ { "extends": "../../tsconfig.package.json", "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo", + // DOM support for canvas operations "lib": ["dom", "dom.iterable", "esnext"] - } + }, + "include": ["src/**/*.ts"] } diff --git a/tsconfig.base.json b/tsconfig.base.json index 4201cfdd..b52ee70a 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,21 +1,54 @@ { "compilerOptions": { - "outDir": "./dist", - // Base strict settings + // Project root for module resolution. + "baseUrl": ".", + + // Path mapping for monorepo packages. + "paths": { + "@nbw/*": ["packages/*"] + }, + + // ============================== + // Global type-safety invariant + // ============================== + + // These rules define the minimum safety bar for the entire repo. + // Disabling any of its sub-rules should be rare and explicitly justified. "strict": true, - "skipLibCheck": true, + + // Disabled to allow compatibility with frameworks and libraries that + // initialize fields outside constructors (e.g. ORMs, DI containers). + "strictPropertyInitialization": false, + + // Prevents subtle runtime bugs caused by switch fallthrough. "noFallthroughCasesInSwitch": true, + + // Enforces consistent file casing across platforms (Windows/macOS/Linux). "forceConsistentCasingInFileNames": true, - // Modern JavaScript features + + // Improves build performance by skipping type-checking of .d.ts files. + // Safe as long as dependencies are trusted. + "skipLibCheck": true, + + // ============================== + // Language & runtime model + // ============================== + + // Target modern runtimes; downleveling is handled by bundlers if needed. "target": "ESNext", "lib": ["ESNext"], - "allowJs": true, + + // Forces explicit module boundaries. + // Prevents accidental global scripts and implicit module inference. "moduleDetection": "force", - // Build performance - "incremental": true, - // Decorator support for backend compatibility + + // ============================== + // Decorators (backend / ORM support) + // ============================== + + // Required for libraries relying on legacy decorators and runtime metadata + // (e.g. class-validator). "experimentalDecorators": true, "emitDecoratorMetadata": true - }, - "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] + } } diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 00000000..a627c79a --- /dev/null +++ b/tsconfig.eslint.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "baseUrl": ".", + "paths": { + "@web/*": ["apps/frontend/src/*"], + "@server/*": ["apps/backend/src/*"] + } + }, + "references": [ + { "path": "apps/frontend" }, + { "path": "apps/backend" }, + { "path": "packages/configs" }, + { "path": "packages/database" }, + { "path": "packages/song" }, + { "path": "packages/sounds" }, + { "path": "packages/thumbnail" } + ] +} diff --git a/tsconfig.json b/tsconfig.json index ef156702..48dd729b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,21 @@ { - "extends": "./tsconfig.base.json", + // Root build orchestrator. + // This file does NOT compile code - only defines project references for `tsc -b`. + // Real projects live in /apps and /packages and extend tsconfig.base.json. + + // This config does not type-check files directly. + // Use `tsc -b` or `tsc -b --noEmit`. "compilerOptions": { - "module": "nodenext", - // Root project specific overrides - "strictNullChecks": true, - "noImplicitAny": true, - "strictBindCallApply": true, - "strictPropertyInitialization": false + "noEmit": true }, - "exclude": [ - "node_modules", - "**/dist/**/*", - "**/dist/**/*", - "**/*.js", - "**/*.mjs" + "files": [], + "references": [ + { "path": "apps/frontend" }, + { "path": "apps/backend" }, + { "path": "packages/configs" }, + { "path": "packages/database" }, + { "path": "packages/song" }, + { "path": "packages/sounds" }, + { "path": "packages/thumbnail" } ] } diff --git a/tsconfig.package.json b/tsconfig.package.json index 7027807e..d4ac7340 100644 --- a/tsconfig.package.json +++ b/tsconfig.package.json @@ -1,17 +1,47 @@ { "extends": "./tsconfig.base.json", "compilerOptions": { - // Modern bundler mode + // ============================== + // Project reference participation + // ============================== + + // Required for tsc --build and incremental compilation. + "composite": true, + + // Packages emit types only; JS output is handled by bundlers. + "noEmit": false, + "emitDeclarationOnly": true, + "declaration": true, + + // ============================== + // Module system (bundler-friendly) + // ============================== + + // Preserve native ESM syntax and let the bundler handle resolution. "module": "ESNext", "moduleResolution": "bundler", - "allowImportingTsExtensions": true, + + // Do not rewrite imports/exports. + // Ensures the emitted .d.ts matches source semantics exactly. "verbatimModuleSyntax": true, - "noEmit": true, - "jsx": "react-jsx", - // Additional strict checks for packages + + // ============================== + // Extra safety for library code + // ============================== + + // Prevents unsafe indexed access (e.g. obj[key] returning undefined). "noUncheckedIndexedAccess": true, + + // Requires `override` keyword when overriding class methods. + // Helps prevent accidental API mismatches. "noImplicitOverride": true, - // Relaxed flags (can be enabled per package) + + // ============================== + // Linting rules + // ============================== + + // Disabled since they are covered by ESLint rules. + // Can be enabled in individual packages if desired. "noUnusedLocals": false, "noUnusedParameters": false, "noPropertyAccessFromIndexSignature": false