Skip to content

Conversation

@Bentroen
Copy link
Member

@Bentroen Bentroen commented Jan 4, 2026

Overview

This PR overhauls the TypeScript configuration (tsconfig.json) for the root project and all subprojects (packages and apps), employing project references to make sure the entire project is seen by the compiler as a cohesive graph.

During this refactor, many configurations were found to have been proactively added, unnecessary, and/or unadvised. We also took the opportunity to normalize and annotate the existing configurations, which should help set a standard for future modifications in this scope. Each commit's message contains a brief description of why we found it appropriate to remove or overhaul those configurations.

The refactor was partially guided by this conversation with ChatGPT, which I've decided to leave here as it provides thorough and useful insight.

Rationale

The motivation for these changes is the following:

  1. Prior to this pull request, running tsc -b in the root folder of the project returned ~2k errors, for which there was no sign of when opening these files in the IDE. This output was inconsistent with the build output in our deployment environment, which made it difficult to figure out which errors were legitimate and what was just noise due to incorrect/mismatched configs.
  2. We were consistently finding ourselves in situations where type-checking errors would only be found at build time:
    • We finish all the work there is to be done, merge it into develop, and only at the time we try to deploy it, we find that the project doesn't build.
    • Although we're running linting and tests during integration (PRs and develop/main branches), type checking is not included in either of these steps, and was only performed at build time.
    • As such, we only learn about these errors when deploying the code, a critical point where everything would ideally be fully working. This has been responsible for delaying many past feature releases, in some cases by many days.
    • This is aggravated by point 1: many of these settings that cause errors weren't even apparent to us in the IDE, making the process of fixing the mistakes very stressful - sometimes even requiring us to resort to trial-and-error through a commit-push-deploy cycle.

During the refactoring, we've found:

  • configurations applied inconsistently/arbitrarily to packages, without an apparent reason why;
  • redundant configurations (defined both in the root tsconfig.json and in one that extends from it);
  • duplicate configurations (that were defined at every project's level rather than being extracted to the base config);
  • rules added or copied-pasted to fix one-off situations or silence warnings, rather than figuring out their root cause;
  • unused tsconfig.build.json variants, with changes that were unnecessary or had no effect on the build process.

While this situation is expected in starting projects, it makes sense to clean it up as part of our path toward reaching maturity.
This PR aims to address all of these situations across every tsconfig.json in the repository, ensuring consistency, enforcing a predictable standard for future config changes, and addressing technical debt left by earlier refactoring work.

Gold standard

With this PR, we aim to introduce a gold standard to configuration management on the project.

When refactoring these rules, we resorted to one core principle:

If we can't justify the rule in one sentence, then it (probably) doesn't belong in the project.

It's better to be thorough and remove more than necessary, and then later re-add what's needed, than to leave things in the configuration that we don't know the purpose of.

Additionally, global configs were fully annotated with descriptive comments.
They're broad enough to deserve careful consideration, which is addressed through a thorough review and by commenting on the purpose of each remaining rule.
We expect future configuration changes to follow this standard. This will eventually be added to our contributing guides as well, as this is something we expect from every contributor to the project.

Changes

  • Adds root tsconfig.json.

    • Uses references to point to all packages in the build graph.
    • Acts as entry point for IDE and tsc -b, ensuring the IDE and type checker see the project exactly the same way.
    • Adds "composite": true to every project's inherited TS config.
    • As a (desirable) side-effect, the root tsconfig.json no longer compiles any code - instead, it just holds references to the depending projects, which are resolved at build time.
      • Requires using -b flag to type check project, which traverses the build graph and resolves all references: tsc -b --noEmit.
  • Reviews settings in every tsconfig.json file across the repo.

    • Removes duplicate configurations (defined both in the root and in extended configs).
    • Removes redundant configurations.
      • Example: flags already included in "strict": true and redefined, such as strictNullChecks
    • Extracts common configurations across every package to the root config, avoiding duplication. These include:
      • tsconfig.base.json (base config; inherited by all apps and tsconfig.package.json:
        • "strict": true -> global project-wide safety standard
      • tsconfig.package.json (package config: inherited by all packages and derived from tsconfig.base.json)
        • "composite": true
        • "noEmit": false
        • "emitDeclarationOnly": true
        • "declaration": true
        • "outDir": "dist"
  • Separated responsibilities of the type-checker and bundler:

    • As we use a bundler rather than tsc to build the project, its only responsibility is to build types (emitDeclarationOnly). As such, the TS config shouldn't be responsible for excluding files from the build process: our bundler should!
    • Removes all instances of tsconfig.build.json variants.
      • In most cases, these would override specific configurations for the build process. Sometimes this would mask errors that only appeared at build time.
      • It was also often employed to override settings such as include and exclude in unnecessary ways; see below.
    • include was standardized to ["src"], including only application code and co-located tests in type-checking.
    • exclude was almost entirely removed:
      • It's not necessary to exclude node_modules if only src is included - besides, TypeScript excludes it by default.
      • Same for dist: it doesn't need to be excluded if not explicitly included.
      • It's not desirable to exclude **/*.spec.ts because it's appropriate to type check tests-- which we now do.
    • Updated package scripts, removing build:types (whose only purpose was to apply the build configuration) and using only tsc b at the end of the build script.
      • As each package is now composite, it doesn't make sense to run as a standalone command. As such, having a separate script is no longer justified.
  • Global configs were fully annotated with descriptive comments:

    • tsconfig.json
    • tsconfig.package.json
    • tsconfig.base.json
  • Adds the following VS Code workspace setting:

    • "typescript.tsdk": "./node_modules/typescript/lib"
    • "typescript.enablePromptUseWorkspaceTsdk": true
    • These prompt the user to use the project's TypeScript version rather than the embedded one.
      • Rationale: VS Code defaults to using its embedded TS version, which could have minor differences from the project's version, resulting in different type errors appearing in the IDE vs. compilation.
  • Adds typecheck.yml workflow to run alongside linting and tests.

    • Adds typecheck script to workspace root.
    • Build errors should now be crystal-clear and apparent to us before even merging new code.
    • Pre-commit remains running only lint and format to keep performance sane.

Results

After merging, we should observe the following scenario:

  • Type checking should now be consistent across:
    • IDE
    • CI
    • Build
  • Configurations are now predictable. Type-checking behavior is consistent across all apps and packages.
  • Configurations are normalized:
    • Settings required in every package are defined in the root config.
    • Specific overrides are only added when necessary, rather than duplicating the root configs.
  • Type checking performed on every integration step, mitigating deployment mishaps.

…data before submit

Conforms to zod v4 whic
All packages should be `composite` so they're aware of each other within the monorepo's context.

We also extract common settings `emitDeclarationOnly`, `declaration` and `outDir` so this standard config is extended by all packages, avoiding the need to redefine them individually per package.

`"noEmit": false` is required for `"composite": true`, so this change is also included here.
strictPropertyInitialization:
- Remained after removing:
"strictNullChecks": true, "noImplicitAny": true, "strictBindCallApply": true,
which are already implied by "strict": true
- Overrides default value for "strict": true to allow for decorators (Nest.js, class-validator etc.)
- May be moved to specific packages instead, but doesn't cause harm if kept

allowJs:
- Likely added due to the adasync.js experiment
- We don't have any other .js files on the repo

incremental:
- Removed because `composite: true` already implies `incremental`
- incremental without composite produces .tsbuildinfo in weird places

exclude:
- if not explicitly included, none of these paths will be part of the TS project
- **/*.spec.ts excludes test files, which should be type-checked as well
allowImportingTsExtensions:
- Unused (unnecessary)

jsx:
- Belongs in the frontend config, not globally
While it's fine (and recommended) to have a separate TS config for building in apps, for packages, it adds unnecessary complexity and possible IDE/build mismatches in error reporting. So we're resorting to one simple rule: unless we can justify why a separate build config is needed in one sentence, it should be out - it's easy to reintroduce something later if things break.

The necessary rules have been merged into the base tsconfig.json and other unnecessary rules have been removed from either/both of them.
This change was prompted by the removal of this line from `database/tsconfig.json` in commit 01e0984:

    "paths": {
      "@database/*": ["src/*"]
    }

This adds an internal alias to the package itself, whereas this would only be needed in cross-package references.
This bypassed package boundaries, creating an internal public API that does not exist. For this reason and to be consistent with the rest of the project, internal imports are being changed to use relative path syntax.
…c -b`

We no longer have a separate `tsconfig.build.ts` specific for building, so there doesn't make sense to be a separate command for building types - it's not meant to be run standalone anyway, as each package is now composite.
Runs against the root `tsconfig.json`, which defines the build graph for the entire project.
The `-b` option actually makes sure the entire chain is built, then reports the errors.
- Removes no-op duplication
- Removes redundant configs (e.g. strictNullChecks, noImplicitAny, strictBindCallApply already included in strict: true)
- Merged `baseUrl` into `paths`
- Removed exclusion of tests, node_modules and dist
- Included `scripts` folder in the config
- Removed relaxed strict settings (pitfall)
- Removed redundant `outDir` (already defined in base config)
- Add missing comment in start section
- Remove tsconfig.build.json (no longer necessary)
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive TypeScript configuration overhaul using project references to create a cohesive build graph across all packages and apps. The refactoring normalizes configurations, removes redundant and unnecessary settings, and adds type-checking to the CI/CD pipeline to catch errors earlier in the development cycle.

Key changes:

  • Introduces root tsconfig.json with project references and composite builds for all packages
  • Consolidates common TypeScript settings into base configurations with detailed annotations
  • Removes separate tsconfig.build.json variants, unifying type-checking responsibility in single configs
  • Updates import paths in database package from path aliases to relative paths

Reviewed changes

Copilot reviewed 28 out of 29 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tsconfig.json Root build orchestrator using project references instead of direct compilation
tsconfig.base.json Base configuration with fully annotated compiler options for all projects
tsconfig.package.json Package-specific configuration with composite build settings and bundler-friendly module system
packages/thumbnail/tsconfig.build.json Removed - no longer needed with unified config approach
packages/thumbnail/package.json Removes build:types script (build still references it - needs fix)
packages/sounds/tsconfig.json Simplified to extend package config with include directive
packages/sounds/tsconfig.build.json Removed - consolidated into main config
packages/sounds/package.json Updates build script to use tsc -b directly
packages/song/tsconfig.json Adds include directive for consistency
packages/song/tsconfig.build.json Removed - consolidated into main config
packages/song/package.json Updates build script to use tsc -b directly
packages/database/tsconfig.json Simplified config (missing include directive)
packages/database/tsconfig.build.json Removed - consolidated into main config
packages/database/src/song/dto/UploadSongResponseDto.dto.ts Updates import from path alias to relative path
packages/database/src/song/dto/SongView.dto.ts Updates imports from path aliases to relative paths
packages/database/src/song/dto/SongPreview.dto.ts Updates import from path alias to relative path
packages/database/src/common/dto/PageQuery.dto.ts Updates import from path alias to relative path
packages/configs/tsconfig.json Simplified to extend package config with include directive
packages/configs/tsconfig.build.json Removed - consolidated into main config
packages/configs/package.json Updates build script to use tsc -b directly
package.json Adds typecheck script for running type-check across all project references
apps/frontend/tsconfig.json Adds composite setting and removes duplicate configuration entries
apps/frontend/src/modules/song/components/client/SongForm.zod.ts Splits form types into Input/Output variants for better type safety
apps/frontend/src/modules/song-upload/components/client/context/UploadSong.context.tsx Updates to use split Input/Output form types with explicit parsing
apps/frontend/src/modules/song-edit/components/client/context/EditSong.context.tsx Updates to use split Input/Output form types with explicit parsing
apps/backend/tsconfig.json Adds composite setting, simplifies config by removing redundant strict flags
apps/backend/tsconfig.build.json Removed - consolidated into main config
.vscode/settings.json Adds workspace TypeScript SDK settings for version consistency
.github/workflows/typecheck.yml New workflow to run type-checking on PRs and branch pushes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
},
"scripts": {
"build": "bun run clean && bun run build:browser && bun run build:node && bun run build:types",
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build script on line 17 still references build:types, which was removed on line 20. The build script should be updated to use tsc -b directly instead of calling the removed build:types script.

Suggested change
"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",

Copilot uses AI. Check for mistakes.
"@database/*": ["src/*"]
}
"emitDecoratorMetadata": true // Necessary for class-validator
}
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing include directive. According to the PR description, all packages should standardize to "include": ["src"]. The database package should include this directive to be consistent with other packages like sounds, song, and configs.

Suggested change
}
},
"include": ["src"]

Copilot uses AI. Check for mistakes.
"paths": {
"@database/*": ["src/*"]
}
"emitDecoratorMetadata": true // Necessary for class-validator
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant configuration. The emitDecoratorMetadata: true setting is already defined in tsconfig.base.json (line 48), which this config extends through tsconfig.package.json. This duplicate declaration should be removed unless there's a specific reason to emphasize it here.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants