Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/typescript-7-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/core-typings': patch
'@rocket.chat/ui-kit': patch
'@rocket.chat/meteor': patch
---

Migrates the workspace to TypeScript 7 (native compiler). The typia toolchain moves to ttsc/tsgo and typia is bumped from a patched 9.7.2 to 13.0.2, with its JSON schema emit normalized to the JSON Schema 2020-12 dialect the runtime Ajv uses (OpenAPI document now reports 3.1.0). No API surface changes.
65 changes: 65 additions & 0 deletions .github/workflows/ci-ts7-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: TS7 Canary

# Non-blocking canary tracking readiness for the TypeScript 7 (native compiler)
# migration. Typechecks the whole monorepo with TS7 while TS5.x still owns
# emit. Does NOT gate merges — it exists to watch the green-package count climb.

on:
workflow_call:
inputs:
node-version:
required: true
type: string
deno-version:
required: true
type: string
workflow_dispatch:
# Nightly: track the green-package count over time without adding cost to PRs.
schedule:
- cron: '0 3 * * *'

jobs:
ts7-typecheck:
runs-on: ubuntu-24.04-arm
name: TS7 TypeCheck (canary)
# Canary: surface results without ever failing the pipeline.
continue-on-error: true

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup NodeJS
uses: ./.github/actions/setup-node
with:
# Empty when dispatched/pushed standalone: setup-node then resolves
# node from package.json engines.node and deno from .tool-versions.
node-version: ${{ inputs.node-version }}
deno-version: ${{ inputs.deno-version }}
cache-modules: true
install: true
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

- uses: rharkor/caching-for-turbo@5d14fba18e450c09393333cfd4242e8b3cb455a6 # v2.4.2

# core-typings and ui-kit now build with ttsc, which compiles typia's
# native Go plugin on first run — needs a Go toolchain + a warm cache.
- name: Setup Go (ttsc typia plugin)
uses: actions/setup-go@v5
with:
go-version: 'stable'

- name: Cache ttsc plugin build
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: '**/node_modules/.cache/ttsc'
key: ttsc-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
restore-keys: |
ttsc-${{ runner.os }}-

# Build packages for fresh `.d.ts` (typia packages emit via ttsc, the
# rest via tsc); otherwise stale dist yields false typecheck errors.
- name: Build workspace packages
run: yarn turbo run build --filter='./packages/*' --filter='./ee/packages/*' --concurrency=5

- name: TS7 typecheck (non-blocking)
run: bash ./scripts/ts7-typecheck.sh
17 changes: 0 additions & 17 deletions .yarn/patches/typia-npm-9.7.2-5c5d9c80b4.patch

This file was deleted.

1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Read the doc that matches the task instead of scanning `docs/` wholesale.

- [docs/meteor-modern-stack.md](docs/meteor-modern-stack.md) — Meteor modern build stack, file-watching caveats
- [docs/coverage.md](docs/coverage.md) — coverage instrumentation in build and CI
- [docs/typescript-7-migration.md](docs/typescript-7-migration.md) — TS7 (native compiler) migration: config changes, packages pinned to TS5.9 and why, canary

### Other

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { IMessage } from '@rocket.chat/core-typings';
import { isThreadMainMessage, isRoomFederated } from '@rocket.chat/core-typings';
import { useLayout, useUser, useUserPreference, useSetting, useEndpoint, useSearchParameter } from '@rocket.chat/ui-contexts';
import type { ReactNode } from 'react';
import type { KeyboardEvent, MouseEvent, ReactNode } from 'react';
import { useMemo, memo } from 'react';

import { getRegexHighlight, getRegexHighlightUrl } from '../../../../../app/highlight-words/client/helper';
Expand Down Expand Up @@ -68,12 +69,13 @@ const MessageListProvider = ({ children, attachmentDimension }: MessageListProvi
() => ({
showColors,
useUserHasReacted: username
? (message) =>
(reaction): boolean =>
? (message: IMessage) =>
(reaction: string): boolean =>
Boolean(message.reactions?.[reaction].usernames.includes(username))
: () => (): boolean => false,
useShowFollowing: uid
? ({ message }): boolean => Boolean(message.replies && message.replies.indexOf(uid) > -1 && !isThreadMainMessage(message))
? ({ message }: { message: IMessage }): boolean =>
Boolean(message.replies && message.replies.indexOf(uid) > -1 && !isThreadMainMessage(message))
: (): boolean => false,

autoTranslate: {
Expand All @@ -82,7 +84,8 @@ const MessageListProvider = ({ children, attachmentDimension }: MessageListProvi
showAutoTranslate,
},
useShowStarred: hasSubscription
? ({ message }): boolean => Boolean(Array.isArray(message.starred) && message.starred.find((star) => star._id === uid))
? ({ message }: { message: IMessage }): boolean =>
Boolean(Array.isArray(message.starred) && message.starred.find((star) => star._id === uid))
: (): boolean => false,
useMessageDateFormatter:
() =>
Expand All @@ -109,8 +112,8 @@ const MessageListProvider = ({ children, attachmentDimension }: MessageListProvi
})),

useOpenEmojiPicker: uid
? (message) =>
(e): void => {
? (message: IMessage) =>
(e: MouseEvent | KeyboardEvent): void => {
e.nativeEvent.stopImmediatePropagation();
chat?.emojiPicker.open(e.currentTarget, (emoji: string) => reactToMessage({ messageId: message._id, reaction: emoji }));
}
Expand Down
2 changes: 2 additions & 0 deletions apps/meteor/ee/server/meteor-methods/saveCannedResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export const saveCannedResponse = async (

result = await CannedResponse.updateCannedResponse(_id, {
...responseData,
tags: responseData.tags ?? [],
...(cannedResponse.scope === 'user' && { userId: cannedResponse.userId }),
createdBy: cannedResponse.createdBy,
});
Expand All @@ -98,6 +99,7 @@ export const saveCannedResponse = async (

const data = {
...responseData,
tags: responseData.tags ?? [],
...(responseData.scope === 'user' && { userId: user?._id }),
createdBy: { _id: user?._id || '', username: user?.username || '' },
_createdAt: new Date(),
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/ee/server/services/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"npm-run-all": "^4.1.5",
"pino-pretty": "13.1.3",
"ts-node": "^10.9.2",
"typescript": "~5.9.3"
"typescript": "~7.0.2"
},
"volta": {
"extends": "../../../../../package.json"
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/jest.config.ts → apps/meteor/jest.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import client from '@rocket.chat/jest-presets/client';
import server from '@rocket.chat/jest-presets/server';
import type { Config } from 'jest';

/** @type {import('jest').Config} */
export default {
projects: [
{
Expand Down Expand Up @@ -60,4 +60,4 @@ export default {
],
coverageProvider: 'v8',
collectCoverage: true,
} satisfies Config;
};
4 changes: 2 additions & 2 deletions apps/meteor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@
"tweetnacl": "^1.0.3",
"twilio": "~5.4.5",
"twitter-api-v2": "^1.29.1",
"typia": "patch:typia@npm%3A9.7.2#~/.yarn/patches/typia-npm-9.7.2-5c5d9c80b4.patch",
"typia": "13.0.2",
"ua-parser-js": "~1.0.41",
"underscore": "^1.13.8",
"universal-perf-hooks": "^1.0.1",
Expand Down Expand Up @@ -465,7 +465,7 @@
"template-file": "^6.0.1",
"ts-node": "^10.9.2",
"tsx": "~4.22.5",
"typescript": "~5.9.3",
"typescript": "~7.0.2",
"webpack": "~5.104.1"
},
"volta": {
Expand Down
3 changes: 2 additions & 1 deletion apps/meteor/server/api/default/openApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ const getTypedRoutes = (
};

const makeOpenAPIResponse = (paths: Record<string, Record<string, Route>>) => ({
openapi: '3.0.3',
// 3.1: the component schemas are typia's JSON Schema 2020-12 output (prefixItems, etc.).
openapi: '3.1.0',
info: {
title: 'Rocket.Chat API',
description: 'Rocket.Chat API',
Expand Down
9 changes: 6 additions & 3 deletions apps/meteor/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,20 @@
"noFallthroughCasesInSwitch": false,

/* Module Resolution Options */
"baseUrl": ".",
"paths": {
/* Support absolute /imports/* with a leading '/' */
"/*": ["*"],
"/*": ["./*"],
/* No .meteor/local/types/packages.d.ts fallback: its `export =` module
declarations take over the module identity under TS7 and discard the
ambient merges from @types/meteor and definition/externals. Everything
it declared is covered by @types/meteor + definition/externals. */
"meteor/*": ["./node_modules/@types/meteor/*"],
"swiper/modules/index.mjs": ["./node_modules/swiper/types/modules/index.d.ts"],
"swiper/swiper-react.mjs": ["./node_modules/swiper/swiper-react.d.ts"],
"swiper/swiper-react": ["./node_modules/swiper/swiper-react.d.ts"],
/* tinykeys 1.x has no "types" condition in its exports map, so bundler
resolution lands on tinykeys.module.js with no adjacent .d.ts. */
"tinykeys": ["./node_modules/tinykeys/dist/tinykeys.d.ts"],
},
"preserveSymlinks": true,

Expand All @@ -38,7 +41,7 @@
// "emitDecoratorMetadata": true,
// "experimentalDecorators": true,
},
"include": ["./**/*", "./.storybook/**/*", "./jest.config.ts", "./.scripts/**/*"],
"include": ["./**/*", "./.storybook/**/*", "./.scripts/**/*"],
"exclude": [
"**/node_modules/**",
"./.meteor/**",
Expand Down
2 changes: 1 addition & 1 deletion apps/uikit-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@types/react-dom": "~19.2.4",
"@vitejs/plugin-react": "~6.0.5",
"eslint": "~9.39.5",
"typescript": "~5.9.3",
"typescript": "~7.0.2",
"vite": "^8.0.16"
},
"volta": {
Expand Down
107 changes: 107 additions & 0 deletions docs/typescript-7-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# TypeScript 7 migration

Status: **in progress** (draft). The workspace default compiler is now
`typescript@~7.0.2` — the native (Go) compiler. A small set of packages stays
pinned to `~5.9.3` because their toolchain still links the old JS compiler
API, which `typescript@7` no longer ships (its main export only exposes the
version; the compiler is a native binary plus the `unstable/*` APIs).

## What changed

- `typescript` devDependency bumped to `~7.0.2` in every workspace except the
pinned ones below.
- `@rocket.chat/tsconfig` base config migrated to TS7-valid options:
- `target`: `es5` → `es2022` (ES3/ES5 targets were removed in TS6/7).
- `moduleResolution`: `node` (node10, removed) → `bundler`. TS7 allows
`module: commonjs` + `moduleResolution: bundler`; TS5.9 did not, which is
why the spike (`chore/ts7-spike`) had to keep the base config on `node`
and inject overrides via CLI flags.
- `types: ["node", "jest"]`: TS7 no longer auto-includes every `@types/*`
package it can see, so global ambient types must be named. Module-scoped
`@types/*` (e.g. `@types/express`) still resolve through imports and need
no listing.
- Explicit `rootDir` added to every tsconfig that sets `outDir` (TS7 requires
it — error TS5011 — instead of inferring the common source directory).
Services that compile meteor sources directly (`authorization-service`,
`ddp-streamer`) root at the repo root, preserving the monorepo-mirrored
dist layout the `ee/apps/Dockerfile` overlay expects.
- `baseUrl` was removed in TS7 (TS5102); `paths` entries now resolve relative
to the tsconfig, and every mapping value must be relative (TS5090).
- Packages pinned to TS5.9 that inherit the base but emit through a bundler
(`livechat`, `emitter`) set `module: "preserve"`: TS5.9 rejects
`commonjs` + `bundler`, `preserve` satisfies both compilers.
- typia packages (`core-typings`, `ui-kit`) build with `ttsc` driving the
`tsgo` binary from `@typescript/native-preview`, with the typia transform
as a Go source plugin (see `chore/ts7-spike`); typia bumped 9.7.2 → 13.0.2
and the 9.7.2 patch was dropped. The typia Go plugin compiles on first run
(ttsc vendors its own Go toolchain via `@ttsc/*`; the TS7 canary workflow
additionally sets up Go and caches the plugin build to keep cold runs fast).
- `apps/meteor`'s `typia` dependency aligned to 13.0.2 so a single typia
runtime/schema dialect exists in the tree (its usages are type-level and
schema-consuming only).
- Vestigial `ts-jest` devDependencies removed (all jest suites run on
`@swc/jest` via `@rocket.chat/jest-presets`). Jest 30 loads
`jest.config.ts` without ts-node under Node 22 in typeless packages
(Node re-parses the config as ESM by syntax detection). `apps/meteor`
declares `"type": "commonjs"`, which disables that fallback — its config
is now `jest.config.mjs` (plain ESM; jest's `.mts` loader also drops the
`projects` array, so `.mjs` is the reliable shape). `server-fetch` moved
from jest 29 (which required ts-node for TS configs) to the workspace's
jest 30. Meteor's mocha suites run on `tsx`, which has no TS API
dependency.
- The repo-root `eslint.config.mjs` imports `globals` directly, so `globals`
is now a root devDependency — previously it resolved by hoisting accident,
and the eslint-config `hoistingLimits` change let an ancient transitive
copy win the root spot, crashing every lint run that used the root
config.
- `scripts/ts7-typecheck.sh` + the `TS7 Canary` workflow (ported from the
spike) now run the workspace compiler with no CLI overrides; the canary
stays non-blocking and tracks the remaining red packages.

## Packages pinned to `typescript@~5.9.3` (and why)

| Package | Reason |
| --- | --- |
| `@rocket.chat/core-typings`, `@rocket.chat/ui-kit` | typia toolchain; they compile with `ttsc`/`tsgo` (TS7-native) but keep TS5.9 for the editor/`typecheck` path until typia's toolchain settles. |
| `@rocket.chat/eslint-config` | `typescript-eslint` peer range is `<6.1.0`; typed linting needs the JS compiler API. `installConfig.hoistingLimits: workspaces` keeps the whole lint toolchain nested beside the pinned TS so every workspace lints through it. |
| `@rocket.chat/message-parser`, `@rocket.chat/livechat` | webpack builds load `webpack.config.ts` through ts-node, which crashes on the TS7 API. |
| `@rocket.chat/emitter`, `@rocket.chat/mp3-encoder` | `@rollup/plugin-typescript` links the JS compiler API. |
| `@rocket.chat/apps` | `node --test` suites load TS through ts-node (extensionless CJS-style relative imports rule out Node's native type stripping); its scripts pass `TS_NODE_COMPILER_OPTIONS` to keep ts-node off the base's `bundler` resolution. |

Unpinning any of these is just a version bump once its tool supports TS7.

## Known caveats (not gating CI)

- `typedoc` (`packages/apps-engine` `gen-doc`, `packages/mp3-encoder`
`docs`) supports TS ≤ 6; those scripts need the pinned TS until typedoc
catches up (mp3-encoder is pinned anyway; apps-engine's is a manual
script, not in CI).
- ts-node-based dev scripts (`yarn fossify` at the root, the `ms` scripts in
`ee/apps/*`, `bench` in message-parser) require the JS compiler API. Where
the package is not pinned, run them with Node 22's native type stripping
(`node file.ts`) or a non-TS-API runner instead.
- `@rocket.chat/federation-sdk` (external) declares a `typescript ~5.9.2`
peer — warning only.

## Canary status

At the time of this migration the canary (`scripts/ts7-typecheck.sh`) is
green for 72 of 73 workspace tsconfigs. The one red package is
`apps/meteor`, with a single root cause repeated ~140 times: TS7 rejects the
`Endpoints` interface simultaneously extending the legacy `ChatEndpoints`
from `@rocket.chat/rest-typings` and the `ExtractRoutesFromAPI` augmentation
that the migrated chat endpoints declare (error TS2320) — the two
declarations of the `/v1/chat.*` routes are no longer considered identical.
Resolving it means finishing the chat portion of the
[API endpoint migration](api-endpoint-migration.md) so each route is
declared exactly once. TS5.9 tolerated the duplicate; nothing behaves
differently at runtime.

## Follow-ups

- Watch typescript-eslint / typedoc / rollup-plugin-typescript for TS7 (or
TS6-bridge) support and drop the pins.
- Move the typia toolchain to a released `typescript@7` binary instead of
`@typescript/native-preview` once ttsc resolves it directly.
- Burn down the remaining red packages in the TS7 canary until it can become
a blocking check.
2 changes: 1 addition & 1 deletion ee/apps/account-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@types/prometheus-gc-stats": "^0.6.4",
"eslint": "~9.39.5",
"ts-node": "^10.9.2",
"typescript": "~5.9.3"
"typescript": "~7.0.2"
},
"volta": {
"extends": "../../../package.json"
Expand Down
1 change: 1 addition & 0 deletions ee/apps/account-service/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
/* Additional Checks */
"noFallthroughCasesInSwitch": false,

"rootDir": "./src",
"outDir": "./dist/ee/apps/account-service/src",
},
"include":
Expand Down
2 changes: 1 addition & 1 deletion ee/apps/authorization-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"@types/prometheus-gc-stats": "^0.6.4",
"eslint": "~9.39.5",
"ts-node": "^10.9.2",
"typescript": "~5.9.3"
"typescript": "~7.0.2"
},
"volta": {
"extends": "../../../package.json"
Expand Down
1 change: 1 addition & 0 deletions ee/apps/authorization-service/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"extends": "@rocket.chat/tsconfig/server.json",
"compilerOptions": {
"strictPropertyInitialization": false, // TODO: Remove this line
"rootDir": "../../..",
"outDir": "./dist"
},
"files": ["./src/service.ts", "./src/fips.ts"],
Expand Down
Loading
Loading