Yuki Takei 1 месяц назад
Родитель
Сommit
fac0f44847

+ 0 - 290
.kiro/specs/remove-ts-node/design.md

@@ -1,290 +0,0 @@
-# Design Document: Remove ts-node
-
-## Overview
-
-**Purpose**: This feature removes the `ts-node` runtime dependency from `apps/app` development and CI scripts, replacing it with Node.js 24's native TypeScript type-stripping. This eliminates the Next.js 15 hook conflict where `nextApp.prepare()` destroys `require.extensions['.ts']` registered by ts-node.
-
-**Users**: GROWI developers and CI pipeline will use Node.js 24 native TypeScript execution for the dev server, migrations, and REPL.
-
-**Impact**: Changes the TypeScript execution mechanism from ts-node (SWC transpilation) to Node.js built-in type erasure. Requires renaming ~64 `.js` files to `.ts` and removing the hook conflict workaround.
-
-### Goals
-- Remove `ts-node/register/transpile-only` from `apps/app` dev/CI scripts
-- Eliminate the `require.extensions['.ts']` hook save/restore workaround
-- Maintain path alias resolution (`~/`, `^/`) without code changes
-- Maintain `dotenv-flow/config` preloading behavior
-- Clean up `ts-node` configuration from `apps/app/tsconfig.json`
-
-### Non-Goals
-- Migrating `apps/slackbot-proxy` (blocked by decorator dependency on `@tsed/*` and TypeORM — deferred to separate work)
-- Removing `ts-node` from root `package.json` `devDependencies` (still needed by slackbot-proxy)
-- Removing `@swc/core` or `tsconfig-paths` from root (used by other tools)
-- Changing the production build pipeline (already compiles to JavaScript via `tsc`)
-
-## Architecture
-
-### Existing Architecture Analysis
-
-The current dev server execution chain is:
-
-```
-node -r ts-node/register/transpile-only -r tsconfig-paths/register -r dotenv-flow/config src/server/app.ts
-```
-
-This loads three preload modules:
-1. **ts-node/register/transpile-only** — registers `.ts` extension handler via `require.extensions['.ts']`, transpiles TypeScript to JavaScript using SWC
-2. **tsconfig-paths/register** — patches `Module._resolveFilename` to resolve `~/` and `^/` path aliases from `tsconfig.json` `paths`
-3. **dotenv-flow/config** — loads `.env*` files into `process.env`
-
-The hook conflict occurs when Next.js 15 loads `next.config.ts`: it registers its own `require.extensions['.ts']` hook, then `deregisterHook()` deletes it — which also destroys ts-node's previously registered hook. The current workaround in `crowi/index.ts` saves and restores the hook around `nextApp.prepare()`.
-
-### Architecture Pattern & Boundary Map
-
-```mermaid
-graph TB
-    subgraph Before
-        Node1[node] --> TSNode[ts-node/register]
-        Node1 --> TscPaths1[tsconfig-paths/register]
-        Node1 --> DotEnv1[dotenv-flow/config]
-        TSNode --> AppTS1[src/server/app.ts]
-        TSNode --> JSFiles[Mixed .js files]
-    end
-
-    subgraph After
-        Node2[node] --> TscPaths2[tsconfig-paths/register]
-        Node2 --> DotEnv2[dotenv-flow/config]
-        Node2 --> NativeTS[Node.js 24 native type-stripping]
-        NativeTS --> AppTS2[src/server/app.ts]
-        NativeTS --> ConvertedTS[Converted .ts files]
-    end
-```
-
-**Architecture Integration**:
-- **Selected pattern**: Remove ts-node from the preload chain; rely on Node.js 24 built-in `.ts` extension handler
-- **Existing patterns preserved**: `tsconfig-paths/register` for path aliases (independent of ts-node), `dotenv-flow/config` for env loading
-- **New components rationale**: None — this is a removal/simplification, not an addition
-- **Steering compliance**: Follows GROWI's Node.js 24 target (`"node": "^24"`)
-
-### Technology Stack
-
-| Layer | Choice / Version | Role in Feature | Notes |
-|-------|------------------|-----------------|-------|
-| Runtime | Node.js 24 | Native TypeScript type-stripping | Default in Node.js 24, no flags needed |
-| Path Resolution | tsconfig-paths 4.2.0 | Runtime `~/` and `^/` alias resolution via `Module._resolveFilename` | Already in use; independent of ts-node (see `research.md`) |
-| Env Loading | dotenv-flow 3.2.0 | `.env` file loading via `-r` flag | No changes needed |
-| Removed | ts-node 10.9.2 | No longer used in `apps/app` | Remains in root for slackbot-proxy |
-
-## Requirements Traceability
-
-| Requirement | Summary | Components | Interfaces | Flows |
-|-------------|---------|------------|------------|-------|
-| 1.1–1.3 | Dev/CI scripts start without ts-node | PackageJsonScripts | — | Dev startup |
-| 1.4–1.5 | Remove ts-node config from tsconfig.json | TsconfigCleanup | — | — |
-| 1.6 | Replace ts-node composite script | PackageJsonScripts | — | — |
-| 2.1–2.4 | Path aliases resolve correctly | PathAliasResolution | — | Module resolution |
-| 3.1–3.3 | Hook conflict workaround removed | HookWorkaroundRemoval | — | Next.js setup |
-| 4.1–4.3 | Mixed .js files continue working | JsToTsConversion | — | — |
-| 5.1–5.2 | dotenv-flow continues loading | PackageJsonScripts | — | Dev startup |
-| 6.1–6.2 | project-dir-utils detects config | — (already implemented) | — | — |
-| 7.1 | Remove ts-node from root devDeps | — (deferred: slackbot-proxy) | — | — |
-| 7.2 | Evaluate tsconfig-paths removal | — (retained: still used) | — | — |
-| 7.3 | Evaluate @swc/core removal | — (retained: VSCode debug, pdf-converter) | — | — |
-| 8.1–8.4 | CI jobs pass | PackageJsonScripts | — | CI pipeline |
-
-## Components and Interfaces
-
-| Component | Domain/Layer | Intent | Req Coverage | Key Dependencies | Contracts |
-|-----------|--------------|--------|--------------|------------------|-----------|
-| PackageJsonScripts | Build Config | Replace ts-node composite script with native TS execution | 1.1–1.3, 1.6, 5.1–5.2, 8.1–8.4 | tsconfig-paths (P0), dotenv-flow (P0) | — |
-| TsconfigCleanup | Build Config | Remove ts-node config sections from tsconfig.json | 1.4–1.5 | — | — |
-| HookWorkaroundRemoval | Server Runtime | Delete require.extensions save/restore workaround | 3.1–3.3 | Next.js (P0) | — |
-| JsToTsConversion | Server Source | Rename .js → .ts for files with ESM import syntax | 4.1–4.3, 2.3 | — | — |
-| EnumToConstConversion | Server Source | Convert UploadStatus enum to const object | — (enabler) | — | Service |
-| PathAliasResolution | Module Resolution | Retain tsconfig-paths/register for ~/  and ^/ aliases | 2.1–2.4 | tsconfig-paths (P0) | — |
-
-### Build Config
-
-#### PackageJsonScripts
-
-| Field | Detail |
-|-------|--------|
-| Intent | Replace the `ts-node` composite script in `apps/app/package.json` with a native Node.js 24 equivalent |
-| Requirements | 1.1–1.3, 1.6, 5.1–5.2, 8.1–8.4 |
-
-**Responsibilities & Constraints**
-- Replace `"ts-node": "node -r ts-node/register/transpile-only -r tsconfig-paths/register -r dotenv-flow/config"` with `"node-dev": "node -r tsconfig-paths/register -r dotenv-flow/config"`
-- Rename the script from `ts-node` to `node-dev` to avoid confusion (ts-node is no longer used)
-- Update all 4 callers that reference `pnpm run ts-node` → `pnpm run node-dev`: `dev`, `launch-dev:ci`, `dev:migrate-mongo`, `repl`
-
-**Dependencies**
-- Outbound: tsconfig-paths/register — path alias resolution (P0)
-- Outbound: dotenv-flow/config — env variable loading (P0)
-- External: Node.js 24 — native `.ts` execution (P0)
-
-**Implementation Notes**
-- `nodemon --exec pnpm run node-dev --inspect` pattern continues to work since nodemon only watches file changes and re-executes the command
-
-#### TsconfigCleanup
-
-| Field | Detail |
-|-------|--------|
-| Intent | Remove the `ts-node` configuration section from `apps/app/tsconfig.json` |
-| Requirements | 1.4–1.5 |
-
-**Responsibilities & Constraints**
-- Remove the `"ts-node": { "transpileOnly": true, "swc": true, "compilerOptions": { ... } }` section
-- The `compilerOptions.module: "CommonJS"` and `moduleResolution: "Node"` settings inside `ts-node` are ts-node-specific overrides; the base config uses `"module": "ESNext"` and `"moduleResolution": "Bundler"` which are correct for the build pipeline
-- Note: `apps/slackbot-proxy/tsconfig.json` cleanup is deferred (out of scope)
-
-### Server Runtime
-
-#### HookWorkaroundRemoval
-
-| Field | Detail |
-|-------|--------|
-| Intent | Remove the `require.extensions['.ts']` save/restore workaround in `crowi/index.ts` |
-| Requirements | 3.1–3.3 |
-
-**Responsibilities & Constraints**
-- Delete lines 557–566 in `src/server/crowi/index.ts` (the `savedTsHook` / `require.extensions` block)
-- Node.js 24 native type-stripping does not register `require.extensions['.ts']` — it uses an internal mechanism that Next.js cannot destroy
-- After removal, the Next.js setup block simplifies to just `this.nextApp = next({ dev }); await this.nextApp.prepare();`
-
-**Dependencies**
-- Inbound: Next.js 15 `nextApp.prepare()` — the trigger for the former conflict (P0)
-
-### Server Source
-
-#### JsToTsConversion
-
-| Field | Detail |
-|-------|--------|
-| Intent | Rename `.js` files with ESM `import` syntax to `.ts` so they execute correctly under Node.js 24 native type-stripping |
-| Requirements | 4.1–4.3, 2.3 |
-
-**Responsibilities & Constraints**
-- Rename 52 files that use ESM `import` syntax (which ts-node currently transpiles to CJS)
-- Optionally rename the remaining 12 pure CJS files for consistency (recommended)
-- No content changes needed inside files — ESM `import` + `module.exports` is valid TypeScript, and `require()` is valid with `esModuleInterop: true`
-- Use `git mv` for each rename to preserve git history
-
-**File Categories** (see `research.md` for full audit):
-
-| Category | Count | Action |
-|----------|-------|--------|
-| Pure ESM (import/export only) | 8 | Rename `.js` → `.ts` |
-| Mixed (ESM import + module.exports) | 44 | Rename `.js` → `.ts` |
-| Pure CJS (require/module.exports only) | 12 | Rename `.js` → `.ts` (optional, for consistency) |
-
-**Implementation Notes**
-- All internal imports of these files use path aliases (`~/`, `^/`) or relative paths without extensions — `tsconfig-paths` and Node.js resolution handle `.ts` extension matching
-- Imports referencing these files from `.ts` callers already omit the extension, so no caller changes are needed
-- A dedicated commit for renames (before any code changes) preserves `git blame` accuracy
-
-#### EnumToConstConversion
-
-| Field | Detail |
-|-------|--------|
-| Intent | Convert `enum UploadStatus` to a const object pattern compatible with Node.js 24 native type-stripping |
-| Requirements | — (enabler for 1.1–1.3) |
-
-**Responsibilities & Constraints**
-- Convert in `src/server/service/file-uploader/multipart-uploader.ts`
-- The enum is used as value comparisons only (`UploadStatus.BEFORE_INIT`, etc.) — no reverse mapping (`UploadStatus[0]`) detected
-- Consumers: `multipart-uploader.spec.ts`, `gcs/multipart-uploader.ts`, `aws/multipart-uploader.ts`
-
-**Contracts**: Service [x]
-
-##### Service Interface
-
-Current:
-```typescript
-export enum UploadStatus {
-  BEFORE_INIT,
-  IN_PROGRESS,
-  COMPLETED,
-  ABORTED,
-}
-```
-
-Target:
-```typescript
-export const UploadStatus = {
-  BEFORE_INIT: 0,
-  IN_PROGRESS: 1,
-  COMPLETED: 2,
-  ABORTED: 3,
-} as const;
-
-export type UploadStatus = (typeof UploadStatus)[keyof typeof UploadStatus];
-```
-
-- Preconditions: All consumers use `UploadStatus.MEMBER_NAME` syntax (no numeric access)
-- Postconditions: `UploadStatus.BEFORE_INIT === 0`, `UploadStatus.IN_PROGRESS === 1`, etc. — identical runtime values
-- Invariants: Consumers require no changes — `UploadStatus` as both value and type is preserved by the dual declaration
-
-### Module Resolution
-
-#### PathAliasResolution
-
-| Field | Detail |
-|-------|--------|
-| Intent | Retain `tsconfig-paths/register` for runtime path alias resolution without ts-node |
-| Requirements | 2.1–2.4 |
-
-**Responsibilities & Constraints**
-- `tsconfig-paths/register` is loaded via `node -r tsconfig-paths/register` before the application entry point
-- It patches `Module._resolveFilename` to intercept `~/` and `^/` imports and resolve them against `tsconfig.json` `paths`
-- This mechanism is independent of ts-node (see `research.md` — zero ts-node dependency, works via standard CJS module resolution)
-- Node.js 24 native type-stripping does not interfere with `Module._resolveFilename` patching
-
-**Dependencies**
-- External: tsconfig-paths 4.2.0 — CJS module resolver patch (P0)
-- External: `apps/app/tsconfig.json` `paths` config — alias definitions (P0)
-
-**Implementation Notes**
-- No code changes needed — the existing `tsconfig-paths/register` mechanism continues to work identically
-- The only change is removing `ts-node/register/transpile-only` from the preload chain
-
-## Testing Strategy
-
-### Unit Tests
-- `multipart-uploader.spec.ts` — verify `UploadStatus` const conversion preserves all value comparisons and error behaviors
-- Existing server-side unit tests — run full suite to detect any breakage from `.js` → `.ts` renames
-
-### Integration Tests
-- Dev server startup (`pnpm run dev`) — verify server starts, loads Next.js, serves pages
-- CI launch (`pnpm run launch-dev:ci`) — verify health check passes
-- Migration scripts (`pnpm run dev:migrate`) — verify database migrations execute
-- REPL (`pnpm run repl`) — verify interactive session starts
-
-### Smoke Tests
-- Path alias resolution: verify `~/` and `^/` imports resolve correctly in renamed `.ts` files
-- `dotenv-flow` loading: verify `.env` variables are available at startup
-- Next.js `next.config.ts` loading: verify no hook conflict without the workaround code
-
-### CI Pipeline
-- `ci-app-lint` — typecheck and biome pass with renamed files
-- `ci-app-test` — all tests pass
-- `ci-app-launch-dev` — dev server starts and responds
-- `test-prod-node24 / build-prod` — production build succeeds (unaffected but verified)
-
-## Migration Strategy
-
-The migration is executed in ordered commits to preserve git history and enable bisection:
-
-```mermaid
-graph LR
-    C1[Commit 1: enum to const] --> C2[Commit 2: git mv .js to .ts]
-    C2 --> C3[Commit 3: update package.json scripts]
-    C3 --> C4[Commit 4: remove hook workaround]
-    C4 --> C5[Commit 5: remove tsconfig ts-node section]
-```
-
-1. **Commit 1**: Convert `enum UploadStatus` to `const` object (isolated, testable)
-2. **Commit 2**: `git mv` all `.js` → `.ts` renames (pure rename, no content changes — preserves blame)
-3. **Commit 3**: Rename `ts-node` script to `node-dev`, remove `ts-node/register/transpile-only`, update all 4 callers
-4. **Commit 4**: Remove hook workaround from `crowi/index.ts`
-5. **Commit 5**: Remove `ts-node` section from `apps/app/tsconfig.json`
-
-Rollback: Each commit is independently revertible. If issues arise, revert commits 3–5 to restore ts-node execution while keeping the file renames.

+ 0 - 231
.kiro/specs/remove-ts-node/gap-analysis.md

@@ -1,231 +0,0 @@
-# Gap Analysis: Remove ts-node
-
-## Executive Summary
-
-- **Scope**: Replace `ts-node` with Node.js 24 native type-stripping in `apps/app` and `apps/slackbot-proxy` dev/CI scripts
-- **Core challenge**: Path alias resolution (`~/`, `^/`) — `tsconfig-paths/register` must remain or be replaced since Node.js native TS does not read `tsconfig.json`
-- **Major blocker for slackbot-proxy**: Heavy use of TypeScript decorators (`@tsed/*`, TypeORM `@Entity`, `@Column`) — Node.js native type-stripping does **not** support decorators, even with `--experimental-transform-types`
-- **CJS/ESM concern**: ~60 `.js` files in `apps/app/src/server/` use ESM `import` syntax but the package has no `"type": "module"` — currently transpiled by ts-node to CJS. Node.js 24 cannot transpile these.
-- **Recommended approach**: Hybrid — straightforward removal for `apps/app` (with `.js` → `.ts` conversions as needed), separate investigation for `apps/slackbot-proxy` due to decorator dependency
-
----
-
-## 1. Current State Investigation
-
-### 1.1 ts-node Usage Map
-
-| Location | Usage | Purpose |
-|---|---|---|
-| `apps/app/package.json:48` | `"ts-node": "node -r ts-node/register/transpile-only -r tsconfig-paths/register -r dotenv-flow/config"` | Composite script used by `dev`, `launch-dev:ci`, `dev:migrate-mongo`, `repl` |
-| `apps/slackbot-proxy/package.json:23` | `"ts-node": "node -r ts-node/register/transpile-only -r tsconfig-paths/register -r dotenv-flow/config"` | Composite script used by `dev`, `dev:ci` |
-| `apps/app/tsconfig.json:30-37` | `"ts-node": { "transpileOnly": true, "swc": true, ... }` | Configures ts-node to use SWC, CJS module output |
-| `apps/slackbot-proxy/tsconfig.json:42-45` | `"ts-node": { "transpileOnly": true, "swc": true }` | Same SWC-based transpile-only mode |
-| `package.json:81` | `"ts-node": "^10.9.2"` (root devDependency) | Hoisted dependency for both apps |
-
-### 1.2 Hook Conflict Workaround
-
-**File**: `apps/app/src/server/crowi/index.ts:557-566`
-
-```typescript
-const savedTsHook = require.extensions['.ts'];
-this.nextApp = next({ dev });
-await this.nextApp.prepare();
-if (savedTsHook && !require.extensions['.ts']) {
-  require.extensions['.ts'] = savedTsHook;
-}
-```
-
-This saves/restores ts-node's `require.extensions['.ts']` hook around `nextApp.prepare()` because Next.js 15's `next.config.ts` transpiler destroys it. With native type-stripping (which does not register `require.extensions`), this workaround becomes unnecessary.
-
-### 1.3 Path Alias Resolution
-
-**Current mechanism**: `tsconfig-paths/register` loaded via `-r` flag reads `tsconfig.json` `paths` at runtime.
-
-**Aliases in use**:
-- `apps/app`: `~/` → `./src/*`, `^/` → `./*`
-- `apps/slackbot-proxy`: `~/` → `./src/*`
-
-**Consumers**: Extensive usage across server code — `project-dir-utils.ts`, `express-init.js`, `dev.js`, route files, services, etc.
-
-### 1.4 CJS/ESM Mixed Files
-
-**~60 `.js` files in `apps/app/src/server/`** use ESM `import` syntax (e.g., `import express from 'express'`) combined with `module.exports` or CJS `require()`. These work today because ts-node transpiles them to CJS. Notable files:
-- `src/server/crowi/dev.js` — ESM imports + `module.exports`
-- `src/server/crowi/express-init.js` — ESM imports + `module.exports`
-- `src/server/routes/*.js` — Mixed ESM/CJS patterns
-- `src/server/models/*.js` — Mixed ESM/CJS patterns
-
-**Neither `apps/app` nor `apps/slackbot-proxy` have `"type": "module"` in package.json.**
-
-Node.js 24 native type-stripping does **not** perform module system conversion. These files will fail with syntax errors if loaded as-is.
-
-### 1.5 Decorator Usage (Critical for slackbot-proxy)
-
-**`apps/slackbot-proxy`** heavily relies on TypeScript decorators via `@tsed/*` and TypeORM:
-- `@Service()`, `@Inject()`, `@Controller()`, `@Middleware()`
-- `@Entity()`, `@Column()`, `@PrimaryGeneratedColumn()`, `@ManyToOne()`
-- `@Get()`, `@Post()`, `@HeaderParams()`, `@BodyParams()`
-
-**Node.js native type-stripping does NOT support decorators** — not even with `--experimental-transform-types`. This is a hard blocker for `apps/slackbot-proxy`.
-
-**`apps/app`** does **not** use decorators in server code. It has one `enum` usage (`UploadStatus` in `multipart-uploader.ts`), which should be converted to a `const` object to remain compatible with native type-stripping without any flags.
-
-### 1.6 project-dir-utils.ts
-
-**File**: `apps/app/src/server/util/project-dir-utils.ts`
-
-Already checks for both `next.config.ts` and `next.config.js`:
-```typescript
-const isCurrentDirRoot = isServer() && (fs.existsSync('./next.config.ts') || fs.existsSync('./next.config.js'));
-```
-
-**Status**: ✅ Requirement 6 is already satisfied. No changes needed.
-
-### 1.7 dotenv-flow Integration
-
-`dotenv-flow/config` is loaded via `-r` flag in the composite `ts-node` script. Since it's a plain CJS module, `node -r dotenv-flow/config` will continue to work with Node.js 24 natively. No changes needed to dotenv-flow integration itself.
-
-### 1.8 @swc/core Dependency
-
-| Package | `@swc/core` Usage |
-|---|---|
-| Root `package.json` | `devDependency` — used by ts-node (SWC mode) and `@swc-node/register` |
-| `apps/pdf-converter` | Independent `devDependency` — uses `@swc-node/register/esm-register` |
-| `.vscode/launch.json` | "Debug: Current File" uses `@swc-node/register` |
-
-**Assessment**: `@swc/core` in root is shared between ts-node and the VSCode debugger. Removing ts-node alone does not justify removing `@swc/core` from root — the VSCode launch config and `@swc-node/register` still need it.
-
----
-
-## 2. Requirement-to-Asset Map
-
-| Requirement | Current Assets | Gap |
-|---|---|---|
-| **R1: Remove ts-node from dev runtime** | `package.json` scripts, tsconfig `ts-node` sections | **Extend**: Replace `-r ts-node/register/transpile-only` with native TS execution |
-| **R2: Maintain path alias resolution** | `tsconfig-paths/register` via `-r` flag | **Unknown**: Need to validate `tsconfig-paths/register` works with Node.js 24 native TS, or find alternative |
-| **R3: Eliminate Next.js hook conflict** | `crowi/index.ts:557-566` workaround | **Simple removal**: Delete 6 lines of hook save/restore code |
-| **R4: CJS/ESM compatibility** | ~60 `.js` files with mixed syntax, transpiled by ts-node | **Missing**: These files need conversion to valid CJS or `.ts` |
-| **R5: dotenv-flow integration** | `-r dotenv-flow/config` in composite script | **No gap**: Works natively with `node -r` |
-| **R6: project-dir-utils config detection** | Already checks both `.ts` and `.js` | **No gap**: Already implemented |
-| **R7: Clean up dependencies** | `ts-node` in root, `tsconfig-paths` in root, `@swc/core` in root | **Constraint**: `@swc/core` needed by VSCode debug config; `tsconfig-paths` may still be needed |
-| **R8: CI compatibility** | CI uses Node.js 24.x, runs `launch-dev:ci`, `test`, `lint` | **Extend**: Update scripts; CI infra already compatible |
-
-### Gap Tags
-
-- **R2**: `Research Needed` — validate `tsconfig-paths/register` + Node.js 24 native TS compatibility
-- **R4**: `Missing` — ~60 `.js` files need conversion or the module system needs adjustment
-- **R7**: `Constraint` — `@swc/core` and `@swc-node/register` are used outside of ts-node
-- **slackbot-proxy decorators**: `Blocker` — decorators are not supported by native type-stripping
-
----
-
-## 3. Implementation Approach Options
-
-### Option A: Minimal Change — Keep `tsconfig-paths`, Convert `.js` Files
-
-**Strategy**: Replace `ts-node/register/transpile-only` with nothing (rely on native TS), keep `tsconfig-paths/register` for path aliases, convert problematic `.js` files to `.ts`.
-
-**Changes required**:
-1. Update `apps/app/package.json` script: `"ts-node": "node -r tsconfig-paths/register -r dotenv-flow/config"`
-2. Update `apps/slackbot-proxy/package.json` similarly (but see blocker below)
-3. Remove `ts-node` sections from both `tsconfig.json` files
-4. Remove hook workaround from `crowi/index.ts`
-5. Convert ~60 `.js` files to `.ts` (or fix their CJS/ESM syntax)
-6. Convert the one `enum UploadStatus` to a `const` object + type union
-7. Remove `ts-node` from root `package.json`
-
-**Trade-offs**:
-- ✅ Minimal conceptual change — `tsconfig-paths/register` is well-understood
-- ✅ Path aliases continue working without code changes
-- ❌ Large file conversion effort (~60 `.js` → `.ts` files)
-- ❌ `tsconfig-paths/register` + native TS compatibility not officially validated
-- ❌ **Blocked for slackbot-proxy** due to decorators
-
-### Option B: Full Native — Use Node.js Subpath Imports
-
-**Strategy**: Replace both `ts-node` and `tsconfig-paths` with Node.js-native mechanisms. Use `package.json` `"imports"` field for path aliases.
-
-**Changes required**:
-1. Everything in Option A
-2. Replace `~/` and `^/` aliases with `#` prefixed subpath imports
-3. Update all import statements across the codebase
-4. Remove `tsconfig-paths` from root `devDependencies`
-5. Update `tsconfig.json` paths to match subpath imports
-
-**Trade-offs**:
-- ✅ Zero external runtime dependencies for TS execution
-- ✅ Uses officially supported Node.js mechanism
-- ❌ **Massive refactor**: hundreds of import statements to change
-- ❌ `#` prefix is less ergonomic than `~/`
-- ❌ Requires updating both `tsconfig.json` (for IDE/build) and `package.json` (for runtime)
-- ❌ Still blocked for slackbot-proxy
-
-### Option C: Hybrid — Phase by Package
-
-**Strategy**: Remove ts-node from `apps/app` first (using Option A approach), defer `apps/slackbot-proxy` until its decorator situation is resolved (e.g., tsed v7 migration or keeping ts-node only for slackbot-proxy).
-
-**Phase 1 — `apps/app`**:
-1. Replace `ts-node` script with `node -r tsconfig-paths/register -r dotenv-flow/config`
-2. Convert the one `enum UploadStatus` to a `const` object (no special flags needed)
-3. Convert `.js` files with mixed ESM/CJS to proper `.ts`
-4. Remove hook workaround
-5. Remove `ts-node` config from `apps/app/tsconfig.json`
-
-**Phase 2 — `apps/slackbot-proxy`** (deferred):
-- Keep ts-node for slackbot-proxy until decorator support is available
-- Or: migrate slackbot-proxy off decorators (separate project)
-- Or: use `@swc-node/register` as ts-node replacement (it supports decorators)
-
-**Phase 3 — Cleanup**:
-- Remove `ts-node` from root only when both apps are migrated
-- Evaluate `tsconfig-paths` removal after validation
-
-**Trade-offs**:
-- ✅ Delivers value incrementally
-- ✅ Resolves the Next.js hook conflict immediately
-- ✅ Does not block on slackbot-proxy's decorator dependency
-- ❌ ts-node remains in root `devDependencies` until Phase 2 completes
-- ❌ Two different TS execution patterns coexist temporarily
-
----
-
-## 4. Implementation Complexity & Risk
-
-**Effort**: **M (3–7 days)**
-- Core script changes are straightforward (S)
-- `.js` → `.ts` file conversions add bulk (M)
-- slackbot-proxy decorator blocker may require investigation (pushes toward L if addressed)
-
-**Risk**: **Medium**
-- `tsconfig-paths/register` + native TS is not officially validated (mitigated by testing)
-- ~60 file conversions carry regression risk (mitigated by existing tests)
-- slackbot-proxy is a hard blocker without decorator support
-
----
-
-## 5. Research Items for Design Phase
-
-1. **`tsconfig-paths/register` + Node.js 24 native TS**: Validate that `node -r tsconfig-paths/register file.ts` correctly resolves `~/` and `^/` aliases without ts-node present. This is the highest priority research item.
-
-2. **slackbot-proxy strategy**: Determine if `@swc-node/register` (already used by `apps/pdf-converter`) can replace ts-node for slackbot-proxy's decorator needs, or if slackbot-proxy should be excluded from scope.
-
-3. **`.js` file conversion scope**: Audit all ~60 `.js` files to determine which actually need conversion vs. which are pure CJS (valid without ts-node). Files using only `require`/`module.exports` need no changes.
-
-4. **VSCode launch.json**: The "Debug: Current File" configuration uses `@swc-node/register`. Evaluate if this should be updated to use native TS or left as-is.
-
----
-
-## 6. Recommendations for Design Phase
-
-**Preferred approach**: **Option C (Hybrid)**
-- Scope Phase 1 to `apps/app` only, which resolves the core Next.js hook conflict
-- Validate `tsconfig-paths/register` compatibility early (spike/PoC)
-- Defer slackbot-proxy to a separate task or Phase 2
-- Audit `.js` files to determine actual conversion scope (many may be pure CJS)
-
-**Key decisions needed**:
-- Whether slackbot-proxy is in scope or deferred
-- Whether to keep `tsconfig-paths/register` or invest in an alternative
-
-**Already decided**:
-- Convert the one `enum UploadStatus` to a `const` object + type union — `--experimental-transform-types` is not needed

+ 0 - 101
.kiro/specs/remove-ts-node/requirements.md

@@ -1,101 +0,0 @@
-# Requirements Document
-
-## Introduction
-
-GROWI's development server (`apps/app` and `apps/slackbot-proxy`) currently uses `ts-node` to execute TypeScript at runtime. This creates an incompatibility with Next.js 15's `next.config.ts` transpilation, which destroys `require.extensions['.ts']` hooks registered by ts-node. Since GROWI targets Node.js 24 (`"node": "^24"`), which has built-in TypeScript type-stripping enabled by default, ts-node can be replaced with Node.js native TypeScript execution. This eliminates the hook conflict, reduces dependencies, and simplifies the runtime stack.
-
-**Scope**: Development and CI environments only. Production already runs compiled JavaScript (`dist/`) and is unaffected.
-
-**Affected packages**:
-- `apps/app` — uses `node -r ts-node/register/transpile-only -r tsconfig-paths/register -r dotenv-flow/config`
-- `apps/slackbot-proxy` — uses the same pattern
-
-**Out of scope**:
-- `apps/pdf-converter` — already uses `@swc-node/register/esm-register` (no ts-node)
-- Production builds — already compile to JavaScript via `next build` / `tsc`
-- Vitest configurations — use `vite-tsconfig-paths` (unrelated to runtime ts-node)
-
-## Requirements
-
-### Requirement 1: Remove ts-node from Development Runtime
-
-**Objective:** As a developer, I want the dev server to run TypeScript using Node.js native type-stripping, so that the ts-node dependency and its configuration are no longer needed at runtime.
-
-#### Acceptance Criteria
-
-1. When the `pnpm run dev` command is executed in `apps/app`, the dev server shall start without loading `ts-node/register/transpile-only`.
-2. When the `pnpm run dev` command is executed in `apps/slackbot-proxy`, the dev server shall start without loading `ts-node/register/transpile-only`.
-3. When the `pnpm run launch-dev:ci` command is executed, the dev server shall start successfully without ts-node.
-4. The `ts-node` configuration section in `apps/app/tsconfig.json` shall be removed.
-5. The `ts-node` configuration section in `apps/slackbot-proxy/tsconfig.json` shall be removed.
-6. The `ts-node` package script in `apps/app/package.json` and `apps/slackbot-proxy/package.json` shall be replaced with an equivalent that uses Node.js native TypeScript execution.
-
-### Requirement 2: Maintain Path Alias Resolution
-
-**Objective:** As a developer, I want `~/` and `^/` path aliases to continue resolving correctly at runtime, so that all existing `import` and `require` statements work without modification.
-
-#### Acceptance Criteria
-
-1. When a TypeScript source file imports a module using the `~/` prefix (e.g., `import x from '~/utils/logger'`), the dev server shall resolve it to the corresponding file under `src/`.
-2. When a TypeScript source file imports a module using the `^/` prefix (e.g., `import x from '^/config/foo'`), the dev server shall resolve it to the corresponding file under the project root.
-3. While the dev server is running, the path alias resolution shall work for both `.ts` and `.js` files.
-4. The path alias resolution mechanism shall not depend on `require.extensions['.ts']` being registered, since Node.js native TypeScript execution does not register it.
-
-### Requirement 3: Eliminate Next.js 15 Hook Conflict
-
-**Objective:** As a developer, I want the Next.js hook conflict workaround to become unnecessary, so that the codebase is simpler and not reliant on brittle hook restoration logic.
-
-#### Acceptance Criteria
-
-1. When `nextApp.prepare()` completes, the dev server shall be able to `require()` TypeScript files without any explicit hook restoration code.
-2. The workaround code in `src/server/crowi/index.ts` that saves and restores `require.extensions['.ts']` shall be removed.
-3. When `next.config.ts` is loaded by Next.js, the dev server's ability to execute TypeScript files shall not be affected.
-
-### Requirement 4: Maintain CJS/ESM Compatibility for Mixed-Format Files
-
-**Objective:** As a developer, I want existing `.js` files that use ESM `import` syntax (transpiled by ts-node today) to continue working, so that no source file changes are required in those files.
-
-#### Acceptance Criteria
-
-1. When the dev server loads `src/server/crowi/dev.js` (which uses ESM `import` syntax in a `.js` file), the file shall execute correctly.
-2. While the dev server is running, files that mix ESM `import` syntax and CommonJS `module.exports` shall be handled without errors.
-3. If a `.js` file cannot be executed by Node.js native TypeScript support alone, the build system shall provide a fallback mechanism or the file shall be converted to `.ts`.
-
-### Requirement 5: Maintain dotenv-flow Integration
-
-**Objective:** As a developer, I want environment variables to continue loading via `dotenv-flow/config` at dev server startup, so that `.env` files are processed as before.
-
-#### Acceptance Criteria
-
-1. When the dev server starts, the `dotenv-flow/config` module shall be loaded before the application entry point executes.
-2. The environment variable loading behavior shall be identical to the current `node -r dotenv-flow/config` approach.
-
-### Requirement 6: Update project-dir-utils.ts Config Detection
-
-**Objective:** As a developer, I want `project-dir-utils.ts` to detect the project root correctly regardless of whether the config file is `.ts` or `.js`, so that runtime path resolution is accurate.
-
-#### Acceptance Criteria
-
-1. The `isCurrentDirRoot` check in `project-dir-utils.ts` shall accept both `next.config.ts` and `next.config.js`.
-2. When `next.config.ts` exists (and `next.config.js` does not), the `projectRoot` shall resolve to `process.cwd()`.
-
-### Requirement 7: Clean Up Unused Dependencies
-
-**Objective:** As a maintainer, I want ts-node and related unnecessary dependencies removed from the workspace, so that the dependency tree is smaller and maintenance burden is reduced.
-
-#### Acceptance Criteria
-
-1. The `ts-node` package shall be removed from the root `package.json` `devDependencies`.
-2. If `tsconfig-paths` is no longer used by any runtime script (only by Vitest via `vite-tsconfig-paths`), it shall be evaluated for removal from `devDependencies`.
-3. The `@swc/core` dependency shall be evaluated: if it was only required for ts-node's SWC mode and is not used elsewhere, it shall be removed.
-
-### Requirement 8: CI Pipeline Compatibility
-
-**Objective:** As a CI engineer, I want all CI jobs (`ci-app-launch-dev`, `ci-app-test`, `ci-app-lint`) to pass with the new TypeScript execution approach, so that the migration does not break the development workflow.
-
-#### Acceptance Criteria
-
-1. When `ci-app-launch-dev` runs, the dev server shall start and respond to health checks successfully.
-2. When `ci-app-test` runs, all existing tests shall pass without modification.
-3. When `ci-app-lint` runs, all lint checks shall pass without new errors.
-4. When `test-prod-node24 / build-prod` runs, the production build shall succeed.

+ 0 - 117
.kiro/specs/remove-ts-node/research.md

@@ -1,117 +0,0 @@
-# Research & Design Decisions: remove-ts-node
-
-## Summary
-- **Feature**: `remove-ts-node`
-- **Discovery Scope**: Extension (modifying existing dev/CI toolchain)
-- **Key Findings**:
-  - `tsconfig-paths/register` is fully independent of ts-node and works with Node.js 24 native TS via CJS `Module._resolveFilename` patching
-  - 52 of 64 `.js` files in `apps/app/src/server/` use ESM `import` syntax and require `.js` → `.ts` conversion
-  - `apps/slackbot-proxy` is blocked by decorator dependency — deferred to separate work
-
-## Research Log
-
-### tsconfig-paths/register + Node.js 24 Native TS Compatibility
-
-- **Context**: Gap analysis identified this as the highest-priority research item. Node.js 24 does not read `tsconfig.json`, so path aliases (`~/`, `^/`) need an independent mechanism.
-- **Sources Consulted**:
-  - [tsconfig-paths source code](https://github.com/dividab/tsconfig-paths) — `register.js`, `config-loader.js`, `match-path-sync.ts`
-  - [Node.js TypeScript documentation](https://nodejs.org/api/typescript.html)
-  - [tsconfig-paths npm](https://www.npmjs.com/package/tsconfig-paths) — README standalone usage
-- **Findings**:
-  - `tsconfig-paths/register` has **zero dependency on ts-node**. Dependencies: `json5`, `minimist`, `strip-bom` only.
-  - It patches `Module._resolveFilename` (CJS resolver) to intercept aliased imports before normal resolution.
-  - Node.js 24 native type-stripping does not modify `Module._resolveFilename` — it only registers an internal `.ts` extension handler for type erasure.
-  - Execution order with `node -r tsconfig-paths/register -r dotenv-flow/config file.ts`: (1) tsconfig-paths patches resolver, (2) dotenv-flow loads env, (3) Node strips types from `.ts` file, (4) aliased `require()` calls go through patched resolver.
-  - The `TS_NODE_PROJECT` / `TS_NODE_BASEURL` env var checks in tsconfig-paths are backward-compatibility shortcuts, not hard dependencies.
-  - GROWI's `apps/app/tsconfig.json` has `paths` but no `baseUrl`. tsconfig-paths handles this correctly — it uses `tsconfig.json`'s directory as the base and only resolves explicit path patterns.
-- **Implications**: `node -r tsconfig-paths/register -r dotenv-flow/config src/server/app.ts` is a valid drop-in replacement for the current `ts-node` composite script. No alternative path resolution mechanism needed.
-
-### .js File Audit: ESM/CJS Categorization
-
-- **Context**: ts-node currently transpiles `.js` files containing ESM `import` syntax to CJS. Without ts-node, Node.js 24 cannot do this conversion. Files must be assessed for conversion needs.
-- **Sources Consulted**: Direct file-by-file read of all 64 `.js` files in `apps/app/src/server/`
-- **Findings**:
-  - **12 files: Pure CJS** — use only `require()`/`module.exports`, no conversion needed
-    - `models/vo/s2s-message.js`, `middlewares/inject-currentuser-to-localvars.js`, `middlewares/auto-reconnect-to-s2s-msg-server.js`, `routes/user.js`, `routes/avoid-session-routes.js`, `util/apiResponse.js`, `util/apiPaginate.js`, `util/formUtil.js`, `util/getToday.js`, `util/express-validator/sanitizer.js`, `util/express-validator/validator.js` (empty), `service/slack-command-handler/slack-command-handler.js`
-  - **8 files: Pure ESM** — use only `import`/`export`, cleanest conversion candidates
-    - `models/serializers/*.js` (4 files), `models/slack-app-integration.js`, `models/user/index.js`, `routes/attachment/api.js`, `util/slack.js`
-  - **44 files: Mixed ESM/CJS** — ESM `import` at top + `module.exports` at bottom (some with inline `require()`)
-    - Routes (`routes/*.js`, `routes/apiv3/*.js`), services (`service/slack-command-handler/*.js`), middlewares, crowi (`dev.js`, `express-init.js`)
-- **Implications**:
-  - All 52 ESM/mixed files can be converted by simple `.js` → `.ts` rename. The ESM `import` syntax is already valid TypeScript. Inline `require()` calls are also valid in TypeScript with `esModuleInterop: true`.
-  - The 12 pure CJS files can remain as `.js` but renaming to `.ts` for consistency is acceptable since `require()`/`module.exports` is valid TypeScript.
-  - Recommended: rename all 64 files to `.ts` for uniformity. This eliminates the CJS/ESM ambiguity entirely.
-
-### Node.js 24 Type-Stripping Limitations
-
-- **Context**: Identify which TypeScript features used in `apps/app` are incompatible with native type-stripping.
-- **Findings**:
-  - **Enums**: 1 usage — `UploadStatus` in `src/server/service/file-uploader/multipart-uploader.ts`. Decision: convert to `const` object.
-  - **Decorators**: None in `apps/app` server code. (slackbot-proxy has heavy decorator use — out of scope)
-  - **Parameter properties**: Not found in server code.
-  - **`import =` syntax**: Not found.
-  - **JSX/TSX**: Server code does not use JSX.
-- **Implications**: With the `enum` → `const` conversion, all `apps/app` server code is compatible with native type-stripping without any flags.
-
-### apps/slackbot-proxy Decorator Blocker
-
-- **Context**: Requirements scope includes both `apps/app` and `apps/slackbot-proxy`.
-- **Findings**:
-  - slackbot-proxy uses `@tsed/*` framework decorators (`@Service`, `@Controller`, `@Get`, `@Post`, etc.) and TypeORM decorators (`@Entity`, `@Column`, etc.)
-  - Node.js 24 does not support decorators, even with `--experimental-transform-types`
-  - `@swc-node/register` (used by `apps/pdf-converter`) supports decorators and could replace ts-node for slackbot-proxy
-- **Implications**: slackbot-proxy migration is deferred to separate work. Phase 1 focuses on `apps/app` only.
-
-## Architecture Pattern Evaluation
-
-| Option | Description | Strengths | Risks / Limitations | Notes |
-|--------|-------------|-----------|---------------------|-------|
-| Keep tsconfig-paths/register | Continue using `-r tsconfig-paths/register` without ts-node | Zero import changes, proven CJS hook, minimal risk | Not officially validated with native TS (but mechanism is sound) | Recommended for Phase 1 |
-| Node.js subpath imports | Replace `~/`/`^/` with `#` prefixed imports | Native Node.js mechanism, no runtime deps | Massive refactor (hundreds of imports), `#` prefix ergonomics | Not recommended |
-| Custom ESM loader | Write `--import` loader hook for path resolution | Full control, ESM compatible | Maintenance burden, non-standard | Overkill for CJS server |
-
-## Design Decisions
-
-### Decision: Keep tsconfig-paths/register for path alias resolution
-- **Context**: `~/` and `^/` path aliases used extensively throughout server code
-- **Alternatives Considered**:
-  1. Node.js subpath imports — requires `#` prefix and massive import refactor
-  2. Custom loader hook — maintenance overhead
-  3. Keep tsconfig-paths/register — no code changes needed
-- **Selected Approach**: Keep `tsconfig-paths/register` loaded via `-r` flag
-- **Rationale**: Mechanism is CJS `Module._resolveFilename` patching, independent of ts-node. Proven pattern, zero import changes.
-- **Trade-offs**: External dependency remains, but it's lightweight (3 deps) and well-maintained
-- **Follow-up**: Validate with smoke test during implementation
-
-### Decision: Rename all .js → .ts in apps/app/src/server/
-- **Context**: 52 of 64 `.js` files use ESM `import` syntax that ts-node was transpiling. Remaining 12 are pure CJS.
-- **Alternatives Considered**:
-  1. Convert only 52 ESM/mixed files — leaves inconsistency
-  2. Convert all 64 to `.ts` — uniform codebase
-  3. Add `"type": "module"` to package.json — massive cascading changes
-- **Selected Approach**: Rename all 64 files to `.ts`
-- **Rationale**: Simplest approach. ESM `import` + `module.exports` is valid TypeScript. Pure CJS `require()` is also valid TypeScript. No code changes needed inside files — just rename.
-- **Trade-offs**: Git history shows rename (mitigated by `git mv`). 12 pure CJS files didn't strictly need it.
-- **Follow-up**: Verify all imports referencing these files resolve correctly after rename
-
-### Decision: Convert enum UploadStatus to const object
-- **Context**: Single `enum` usage blocks native type-stripping without `--experimental-transform-types`
-- **Selected Approach**: Replace `enum UploadStatus { ... }` with `const UploadStatus = { ... } as const` + type union
-- **Rationale**: Avoids experimental flag dependency. Functional behavior identical.
-
-### Decision: Defer apps/slackbot-proxy to separate work
-- **Context**: Decorator usage makes native type-stripping impossible
-- **Selected Approach**: Phase 1 covers `apps/app` only. slackbot-proxy keeps ts-node or migrates to `@swc-node/register` in a separate effort.
-- **Rationale**: Unblocks the primary goal (Next.js hook conflict resolution) without being held up by an unrelated constraint.
-
-## Risks & Mitigations
-- `tsconfig-paths/register` untested with native TS → Mitigated by smoke test in CI
-- 64 file renames carry git blame disruption → Mitigated by `git mv` and separate rename commit
-- Mixed ESM/CJS patterns may have edge cases → Mitigated by existing test suite coverage
-- ts-node remains in root `devDependencies` for slackbot-proxy → Acceptable temporary state
-
-## References
-- [tsconfig-paths GitHub](https://github.com/dividab/tsconfig-paths) — standalone CJS usage documentation
-- [Node.js TypeScript docs](https://nodejs.org/api/typescript.html) — native type-stripping limitations
-- [Node.js Running TypeScript Natively](https://nodejs.org/en/learn/typescript/run-natively) — official guide
-- [Amaro (Node.js TS engine)](https://github.com/nodejs/amaro) — SWC-based type erasure

+ 0 - 22
.kiro/specs/remove-ts-node/spec.json

@@ -1,22 +0,0 @@
-{
-  "feature_name": "remove-ts-node",
-  "created_at": "2026-03-02T00:00:00.000Z",
-  "updated_at": "2026-03-02T04:00:00.000Z",
-  "language": "en",
-  "phase": "tasks-generated",
-  "approvals": {
-    "requirements": {
-      "generated": true,
-      "approved": true
-    },
-    "design": {
-      "generated": true,
-      "approved": true
-    },
-    "tasks": {
-      "generated": true,
-      "approved": false
-    }
-  },
-  "ready_for_implementation": false
-}

+ 0 - 83
.kiro/specs/remove-ts-node/tasks.md

@@ -1,83 +0,0 @@
-# Implementation Plan
-
-- [ ] 1. Convert enum UploadStatus to const object
-- [ ] 1.1 (P) Replace the enum declaration with a const object and type union in the multipart uploader module
-  - Change `enum UploadStatus { BEFORE_INIT, IN_PROGRESS, COMPLETED, ABORTED }` to a `const` object with `as const` and a corresponding type alias
-  - Preserve numeric values (0, 1, 2, 3) to maintain identical runtime behavior
-  - Verify all consumers use named member access (`UploadStatus.BEFORE_INIT`) — no reverse numeric lookups
-  - _Requirements: 1.1, 1.2, 1.3_
-  - _Contracts: EnumToConstConversion Service_
-
-- [ ] 1.2 (P) Run existing unit tests for the multipart uploader to confirm the const conversion is behavior-preserving
-  - Execute the multipart-uploader spec covering all upload status transitions and error cases
-  - Confirm GCS and AWS multipart uploader subclasses pass without modification
-  - _Requirements: 8.2_
-
-- [ ] 2. Rename server-side .js files to .ts
-- [ ] 2.1 Rename all 64 `.js` files in `apps/app/src/server/` to `.ts` using `git mv`
-  - Rename the 52 files that use ESM `import` syntax (mandatory — these fail without ts-node transpilation)
-  - Rename the 12 pure CJS files for consistency (recommended — `require`/`module.exports` is valid TypeScript)
-  - Use `git mv` for each file to preserve git blame history
-  - No content changes inside any file — ESM `import` + `module.exports` is valid TypeScript with `esModuleInterop: true`
-  - Callers import these files via path aliases (`~/`, `^/`) or relative paths without extensions, so no caller modifications are needed
-  - _Requirements: 4.1, 4.2, 4.3_
-
-- [ ] 2.2 Verify typecheck and lint pass after renames
-  - Run `pnpm lint:typecheck` in apps/app directory to confirm TypeScript accepts the renamed files
-  - Run `pnpm lint:biome` in apps/app directory to confirm no lint regressions
-  - Fix any type errors that surface from stricter checking on newly-typed files (if any)
-  - _Requirements: 8.3_
-
-- [ ] 3. Update package.json scripts to use native Node.js TypeScript execution
-- [ ] 3.1 Replace the `ts-node` composite script with `node-dev` in `apps/app/package.json`
-  - Rename script key from `ts-node` to `node-dev`
-  - Change value from `node -r ts-node/register/transpile-only -r tsconfig-paths/register -r dotenv-flow/config` to `node -r tsconfig-paths/register -r dotenv-flow/config`
-  - Update all 4 callers: `dev`, `launch-dev:ci`, `dev:migrate-mongo`, `repl` — change `pnpm run ts-node` to `pnpm run node-dev` (and `npm run ts-node` to `pnpm run node-dev` in `repl`)
-  - Verify `dotenv-flow/config` remains in the preload chain for environment variable loading
-  - Verify `tsconfig-paths/register` remains in the preload chain for path alias resolution
-  - _Requirements: 1.1, 1.2, 1.3, 1.6, 2.1, 2.2, 2.3, 2.4, 5.1, 5.2_
-  - _Contracts: PackageJsonScripts, PathAliasResolution_
-
-- [ ] 4. Remove the Next.js hook conflict workaround
-- [ ] 4.1 (P) Delete the `require.extensions['.ts']` save/restore block in the server startup
-  - Remove the code that saves `require.extensions['.ts']` before `nextApp.prepare()` and restores it afterward
-  - Remove the associated comments explaining the ts-node hook conflict
-  - Simplify the Next.js setup to just `this.nextApp = next({ dev }); await this.nextApp.prepare();`
-  - Node.js 24 native type-stripping uses an internal mechanism that Next.js cannot interfere with
-  - _Requirements: 3.1, 3.2, 3.3_
-  - _Contracts: HookWorkaroundRemoval_
-
-- [ ] 5. Clean up ts-node configuration
-- [ ] 5.1 (P) Remove the `ts-node` section from `apps/app/tsconfig.json`
-  - Delete the `"ts-node": { "transpileOnly": true, "swc": true, "compilerOptions": { "module": "CommonJS", "moduleResolution": "Node" } }` block
-  - The `module: "CommonJS"` and `moduleResolution: "Node"` overrides were ts-node-specific; the base config (`module: "ESNext"`, `moduleResolution: "Bundler"`) remains correct for the build pipeline
-  - `apps/slackbot-proxy/tsconfig.json` is out of scope (deferred)
-  - _Requirements: 1.4, 1.5_
-  - _Contracts: TsconfigCleanup_
-
-- [ ] 6. Validate the complete migration
-- [ ] 6.1 Run the full test suite to confirm no regressions
-  - Execute `turbo run test --filter @growi/app` to run all unit and integration tests
-  - Verify all tests pass without modification (path aliases, imports, and module resolution unchanged)
-  - _Requirements: 8.2_
-
-- [ ] 6.2 Run typecheck and lint to confirm build integrity
-  - Execute `turbo run lint --filter @growi/app`
-  - _Requirements: 8.3_
-
-- [ ] 6.3 Verify dev server launch in CI mode
-  - Execute `pnpm run launch-dev:ci` and confirm the server starts and responds to health checks
-  - Confirms path alias resolution, dotenv-flow loading, and Next.js startup all work without ts-node
-  - _Requirements: 8.1, 2.1, 2.2, 2.3, 2.4, 5.1, 5.2, 6.1, 6.2_
-
-- [ ] 6.4 Verify production build is unaffected
-  - Execute `turbo run build --filter @growi/app` to confirm the production build succeeds
-  - Production build uses `tsc` and `next build`, which are independent of the dev runtime changes
-  - _Requirements: 8.4_
-
-## Deferred Requirements
-
-The following requirements are intentionally deferred due to the `apps/slackbot-proxy` decorator blocker:
-- **7.1** (Remove ts-node from root devDependencies): ts-node remains in root for slackbot-proxy
-- **7.2** (Evaluate tsconfig-paths removal): tsconfig-paths is actively used by the new `node-dev` script
-- **7.3** (Evaluate @swc/core removal): @swc/core is used by VSCode debug config and pdf-converter