diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d076c84280..d22b8768c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,3 +1,4 @@ +# CI workflow for egg monorepo name: CI on: @@ -27,8 +28,8 @@ jobs: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 + - name: Setup utoo + uses: utooland/setup-utoo@3a51006d0b66afcc32d1b9177a4b200b74f4a8cb # main - name: Set up Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 @@ -36,22 +37,22 @@ jobs: node-version: '24' - name: Install dependencies - run: pnpm install --no-frozen-lockfile + run: ut install --from pnpm - name: Run lint - run: pnpm run lint + run: ut run lint - name: Run typecheck - run: pnpm run typecheck + run: ut run typecheck - name: Run format check - run: pnpm run fmtcheck + run: ut run fmtcheck - name: Run build - run: pnpm run build + run: ut run build - name: Run site build - run: pnpm run site:build + run: ut run site:build test: strategy: @@ -150,8 +151,8 @@ jobs: # & mysqladmin -u root password root & mysql -uroot -e "CREATE DATABASE IF NOT EXISTS test;" - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 + - name: Setup utoo + uses: utooland/setup-utoo@3a51006d0b66afcc32d1b9177a4b200b74f4a8cb # main - name: Set up Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 @@ -159,15 +160,15 @@ jobs: node-version: ${{ matrix.node }} - name: Install dependencies - run: pnpm install --no-frozen-lockfile + run: ut install --from pnpm - name: Run tests - run: pnpm run ci + run: ut run ci - name: Run example tests if: ${{ matrix.os != 'windows-latest' }} run: | - pnpm run example:test:all + ut run example:test:all - name: Code Coverage # skip on windows, it will hangup on codecov @@ -194,8 +195,8 @@ jobs: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 + - name: Setup utoo + uses: utooland/setup-utoo@3a51006d0b66afcc32d1b9177a4b200b74f4a8cb # main - name: Set up Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 @@ -203,12 +204,12 @@ jobs: node-version: ${{ matrix.node }} - name: Install dependencies - run: pnpm install --no-frozen-lockfile + run: ut install --from pnpm - name: Run tests run: | - pnpm build --workspace ./tools/egg-bin - pnpm run --filter ./tools/egg-bin ci + ut run build --workspace @eggjs/bin + ut run ci --workspace @eggjs/bin - name: Code Coverage # skip on windows, it will hangup on codecov https://github.com/codecov/codecov-action/issues/1787 @@ -235,8 +236,8 @@ jobs: - name: Checkout repository uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - - name: Install pnpm - uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4 + - name: Setup utoo + uses: utooland/setup-utoo@3a51006d0b66afcc32d1b9177a4b200b74f4a8cb # main - name: Set up Node.js uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 @@ -244,12 +245,12 @@ jobs: node-version: ${{ matrix.node }} - name: Install dependencies - run: pnpm install --no-frozen-lockfile + run: ut install --from pnpm - name: Run tests run: | - pnpm build - pnpm run --filter=./tools/scripts ci + ut run build -- --workspace ./tools/scripts + ut run ci --workspace tools/scripts - name: Code Coverage if: ${{ matrix.os != 'windows-latest' }} diff --git a/.gitignore b/.gitignore index 08ec8824a2..b89f7ba95d 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,5 @@ tegg/plugin/tegg/test/fixtures/apps/**/*.js ecosystem-ci/cnpmcore ecosystem-ci/examples pnpm-lock.yaml +.utoo.toml +.claude/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json index ca1f443d59..ef430a700b 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -8,7 +8,8 @@ "packages/core/test/fixtures/load_dirs/syntax_error/*", "packages/core/test/fixtures/syntaxerror/*", "packages/core/test/fixtures/load_context_syntax_error/**/*", - "CHANGELOG.md" + "CHANGELOG.md", + "/package.json" ], "experimentalSortImports": { "groups": [ diff --git a/ecosystem-ci/repo.json b/ecosystem-ci/repo.json index dda56ae1bf..7a59ab891e 100644 --- a/ecosystem-ci/repo.json +++ b/ecosystem-ci/repo.json @@ -2,7 +2,7 @@ "cnpmcore": { "repository": "https://github.com/cnpm/cnpmcore.git", "branch": "master", - "hash": "e82df3f4093c0ec9fd5354563605e60f7f613035" + "hash": "98463c33188bbd32513a74e6c3a2c4cc559ef5da" }, "examples": { "repository": "https://github.com/eggjs/examples.git", diff --git a/package.json b/package.json index 4e3e8a01cc..db8806f56b 100644 --- a/package.json +++ b/package.json @@ -13,26 +13,25 @@ ], "type": "module", "scripts": { - "clean-dist": "pnpm -r --parallel exec rimraf dist", - "clean": "pnpm -r --parallel run clean && pnpm clean-dist", + "clean-dist": "ut run clean --workspaces --if-present", "build": "tsdown", - "prelint": "pnpm clean-dist", + "prelint": "ut run clean-dist", "lint": "oxlint --type-aware --type-check --quiet", "fmt": "oxfmt", - "typecheck": "pnpm clean && pnpm -r run typecheck", + "typecheck": "ut run clean-dist && ut run typecheck --workspaces --if-present", "fmtcheck": "oxfmt --check .", - "pretest": "pnpm run clean && pnpm -r run pretest", + "pretest": "ut run clean-dist && ut run pretest --workspaces --if-present", "test": "vitest run --bail 1 --retry 2 --testTimeout 20000 --hookTimeout 20000", - "test:cov": "pnpm run test --coverage", - "preci": "pnpm -r --parallel run pretest", - "ci": "pnpm run test --coverage", - "site:dev": "pnpm --filter=site run dev", - "site:build": "pnpm --filter=site run build", + "test:cov": "ut run test -- --coverage", + "preci": "ut run pretest --workspaces --if-present", + "ci": "ut run test -- --coverage", + "site:dev": "ut run dev --workspace site", + "site:build": "ut run build --workspace site", "puml": "puml . --dest ./site", - "example:dev:commonjs": "pnpm --filter=helloworld-commonjs run dev", - "example:dev:typescript": "pnpm --filter=helloworld-typescript run dev", - "example:dev:tegg": "pnpm --filter=helloworld-tegg run dev", - "example:test:all": "pnpm --filter=helloworld-* run test", + "example:dev:commonjs": "ut run dev --workspace helloworld-commonjs", + "example:dev:typescript": "ut run dev --workspace helloworld-typescript", + "example:dev:tegg": "ut run dev --workspace helloworld-tegg", + "example:test:all": "ut run test --workspace helloworld-typescript && ut run test --workspace helloworld-tegg", "prepare": "husky", "version:patch": "node scripts/version.js patch", "version:minor": "node scripts/version.js minor", @@ -69,6 +68,7 @@ "typescript": "catalog:", "unplugin-unused": "catalog:", "urllib": "catalog:", + "utoo": "catalog:", "vitest": "catalog:" }, "lint-staged": { @@ -80,5 +80,18 @@ "engines": { "node": ">=22.18.0" }, - "packageManager": "pnpm@10.28.0" + "packageManager": "pnpm@10.28.0", + "workspaces": [ + "packages/*", + "plugins/*", + "examples/*", + "tools/*", + "site", + "tegg/core/*", + "tegg/plugin/*", + "tegg/standalone/*" + ], + "overrides": { + "vite": "npm:rolldown-vite@^7.1.13" + } } diff --git a/packages/cluster/test/options.test.ts b/packages/cluster/test/options.test.ts index b1ab8b966d..90bab03680 100644 --- a/packages/cluster/test/options.test.ts +++ b/packages/cluster/test/options.test.ts @@ -239,10 +239,12 @@ describe('test/options.test.ts', () => { baseDir, }); const expectPaths = [ - // run int workspace root + // run in workspace root path.join(__dirname, '../../egg'), - // run in project root + // run in project root (pnpm nested) path.join(__dirname, '../node_modules/egg'), + // run with flat/hoisted node_modules (e.g. ut install) + path.join(__dirname, '../../../node_modules/egg'), ]; assert( expectPaths.includes(options.framework), diff --git a/packages/tsconfig/test/index.test.ts b/packages/tsconfig/test/index.test.ts index 3101478354..6f1cfc1e40 100644 --- a/packages/tsconfig/test/index.test.ts +++ b/packages/tsconfig/test/index.test.ts @@ -1,11 +1,14 @@ import fs from 'node:fs/promises'; +import { createRequire } from 'node:module'; import path from 'node:path'; import coffee from 'coffee'; import { test, expect } from 'vitest'; +const require = createRequire(import.meta.url); + test('should tsc build work', async () => { - const tsc = path.join(import.meta.dirname, '..', 'node_modules', 'typescript', 'bin', 'tsc'); + const tsc = require.resolve('typescript/bin/tsc'); const fixturePath = path.join(import.meta.dirname, 'fixtures/apps/ts-proj'); const tsconfigPath = path.join(fixturePath, 'tsconfig.json'); console.log('%s -p %s, cwd: %s', tsc, tsconfigPath, fixturePath); diff --git a/packages/utils/src/import.ts b/packages/utils/src/import.ts index 8a6e036e21..4e026ad23c 100644 --- a/packages/utils/src/import.ts +++ b/packages/utils/src/import.ts @@ -350,8 +350,47 @@ export function importResolve(filepath: string, options?: ImportResolveOptions): try { moduleFilePath = import.meta.resolve(filepath); } catch (err) { + // === Fallback 1: require.resolve for legacy CJS subpaths === + // + // When `import.meta.resolve(filepath)` throws ERR_MODULE_NOT_FOUND, + // fall back to `require.resolve(filepath, { paths })` so callers + // can still resolve "legacy" CJS subpaths. + // + // This is reached for two combined reasons: + // + // 1. Node.js 22+ ESM resolver is strict about packages **without** + // an `exports` field: subpaths must include an explicit file + // extension (e.g. `tsconfig-paths/register.js`). Bare subpaths + // like `tsconfig-paths/register` cause the resolver to throw, + // because ESM does not auto-append `.js`/`.json`/`.node` the + // way the CJS resolver does. + // + // 2. The manual node_modules walk above (`tryToResolve...`) + // only checks `${p}/node_modules/` plus two pnpm + // sibling levels — it does **not** walk up the directory + // tree the way Node's CJS resolver does. So a dependency + // hoisted to a workspace-root `node_modules/` (typical for + // pnpm/yarn workspaces) is not found by the manual walk and + // falls through to `import.meta.resolve`. + // + // `require.resolve(..., { paths })` handles both: it auto-appends + // extensions AND walks up the directory tree from each `paths` + // entry until it finds the package. Removing this fallback would + // be a breaking change for `@eggjs/utils` consumers (both internal + // — e.g. `tools/egg-bin/src/baseCommand.ts` resolving + // `tsconfig-paths/register` — and any downstream npm package that + // imports `importResolve`). + // + // If `require.resolve` also fails, throw the original ESM error + // so the user sees the ESM resolver's diagnostic, not the CJS one. debug('[importResolve:error] import.meta.resolve %o => %o, options: %o', filepath, err, options); - throw new ImportResolveError(filepath, paths, err as Error); + try { + moduleFilePath = getRequire().resolve(filepath, { paths }); + debug('[importResolve:requireResolveFallback] %o => %o', filepath, moduleFilePath); + return moduleFilePath; + } catch { + throw new ImportResolveError(filepath, paths, err as Error); + } } if (moduleFilePath.startsWith('file://')) { // resolve will return file:// URL on Linux and MacOS expect on Windows diff --git a/packages/utils/test/import.test.ts b/packages/utils/test/import.test.ts index 93b760e7b3..a664539171 100644 --- a/packages/utils/test/import.test.ts +++ b/packages/utils/test/import.test.ts @@ -107,8 +107,8 @@ describe('test/import.test.ts', () => { assert.equal(err.name, 'ImportResolveError'); assert.equal(err.filepath, 'tsconfig-paths-demo-not-exists/register'); assert.deepEqual(err.paths, [getFilepath('cjs/node_modules/inject')]); - assert.match(err.stack ?? '', /Cannot find package/); - assert.match(err.message, /Cannot find package/); + assert.match(err.stack ?? '', /Cannot find (package|module)/); + assert.match(err.message, /Cannot find (package|module)/); return true; }, ); diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 691a60a336..1e1a086d8e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -213,6 +213,7 @@ catalog: urijs: ^1.19.11 urllib: ^4.8.2 utility: ^2.5.0 + utoo: ^1 vary: ^1.1.2 vitepress: 2.0.0-alpha.15 vitepress-plugin-llms: ^1.10.0 @@ -227,6 +228,9 @@ catalogs: path-to-regexp1: path-to-regexp: ^1.9.0 +onlyBuiltDependencies: + - utoo + minimumReleaseAge: 1440 minimumReleaseAgeExclude: diff --git a/tegg/plugin/controller/src/lib/impl/mcp/MCPControllerRegister.ts b/tegg/plugin/controller/src/lib/impl/mcp/MCPControllerRegister.ts index e19d922b2d..bef8dfeae3 100644 --- a/tegg/plugin/controller/src/lib/impl/mcp/MCPControllerRegister.ts +++ b/tegg/plugin/controller/src/lib/impl/mcp/MCPControllerRegister.ts @@ -23,7 +23,6 @@ import { isInitializeRequest, isJSONRPCRequest } from '@modelcontextprotocol/sdk import type { JSONRPCMessage, MessageExtraInfo } from '@modelcontextprotocol/sdk/types.js'; // @ts-expect-error await-event is not typed import awaitEvent from 'await-event'; -// @ts-expect-error content-type is not typed import contentType from 'content-type'; import type { Application, Context, Router } from 'egg'; import compose from 'koa-compose'; diff --git a/tegg/plugin/mcp-proxy/src/index.ts b/tegg/plugin/mcp-proxy/src/index.ts index a89d29c1c0..a993692c4a 100644 --- a/tegg/plugin/mcp-proxy/src/index.ts +++ b/tegg/plugin/mcp-proxy/src/index.ts @@ -13,12 +13,10 @@ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/ import awaitEvent from 'await-event'; // @ts-expect-error cluster-client is not typed import { APIClientBase } from 'cluster-client'; -// @ts-expect-error content-type is not typed import contentType from 'content-type'; import type { Application, Context } from 'egg'; import type { EggLogger } from 'egg'; import { EventSourceParserStream } from 'eventsource-parser/stream'; -// @ts-expect-error koa-compose is not typed import compose from 'koa-compose'; import getRawBody from 'raw-body'; diff --git a/tools/egg-bin/package.json b/tools/egg-bin/package.json index ba4642aa66..2f1318aef8 100644 --- a/tools/egg-bin/package.json +++ b/tools/egg-bin/package.json @@ -51,10 +51,10 @@ } }, "scripts": { + "build": "tsdown -c tsdown.config.ts", "typecheck": "tsgo --noEmit", - "pretest": "tsdown", "test": "vitest run", - "cov": "vitest run --coverage", + "cov": "vitest run --coverage -c vitest.config.ts", "ci": "npm run cov" }, "dependencies": { diff --git a/tools/egg-bin/tsdown.config.ts b/tools/egg-bin/tsdown.config.ts index 0f51f29776..ed904dad5b 100644 --- a/tools/egg-bin/tsdown.config.ts +++ b/tools/egg-bin/tsdown.config.ts @@ -1,8 +1,13 @@ import { defineConfig } from 'tsdown'; export default defineConfig({ + entry: 'src/**/*.ts', unbundle: true, fixedExtension: false, + external: [/^@eggjs\//, 'egg'], + exports: { + devExports: true, + }, // MEMO: @oclif/core only works in unbundle mode (already default) unused: { level: 'error', diff --git a/tools/scripts/package.json b/tools/scripts/package.json index 4bb3deb287..5e33c4e106 100644 --- a/tools/scripts/package.json +++ b/tools/scripts/package.json @@ -50,8 +50,8 @@ "scripts": { "typecheck": "tsgo --noEmit", "test": "vitest run --bail 1 --no-file-parallelism", - "cov": "pnpm test --coverage", - "ci": "pnpm run cov" + "cov": "ut run test -- --coverage", + "ci": "ut run cov" }, "dependencies": { "@eggjs/utils": "workspace:*", diff --git a/tsdown.config.ts b/tsdown.config.ts index 389c514ae1..12ff5fa10c 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -22,6 +22,7 @@ export default defineConfig({ publint: { level: 'suggestion', strict: true, + pack: 'npm', }, // Default entry pattern - glob to include all source files