diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 00000000..95a3bae1 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,36 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "categories": { + "correctness": "off" + }, + "plugins": ["typescript"], + "options": { + "typeAware": true, + "respectEslintDisableDirectives": true + }, + "rules": { + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksVoidReturn": { + "arguments": false, + "attributes": false + } + } + ] + }, + "ignorePatterns": [ + "**/dist/**", + "**/node_modules/**", + "**/*.d.ts", + "**/*.js", + "**/*.mjs", + "**/*.cjs", + "**/*.vue", + "legacy/**", + "packages/infra/prisma/client/**", + "**/generated/**", + ".pnpm-store/**" + ] +} diff --git a/README.md b/README.md index fb29b5b7..132d8b97 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,11 @@ pnpm build pnpm test:integration ``` +`pnpm lint`는 ESLint의 일반 TypeScript/Vue 검사, Oxlint의 type-aware TypeScript +Promise 검사, ESLint의 type-aware Vue Promise 검사를 메모리 경계에 맞게 나누어 +실행합니다. cache를 제외한 전체 검증은 `pnpm lint:uncached`, package 하나만 +검사할 때는 기존처럼 `pnpm --filter lint`를 사용합니다. + 루트 `test`와 `build`는 Codex와 일반 개발 검증에서 성공 로그를 줄이는 간단 출력이 기본입니다. 테스트별 진행과 전체 Turbo task 로그가 필요한 실패 진단에는 `pnpm test-verbose`, `pnpm build-verbose`를 사용합니다. Gateway 서버 빌드 워커는 diff --git a/app/game-api/package.json b/app/game-api/package.json index 2a57a2c9..582603df 100644 --- a/app/game-api/package.json +++ b/app/game-api/package.json @@ -20,8 +20,8 @@ "worker:auction:dev": "GAME_API_ROLE=auction-worker tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api --watch", "worker:tournament": "GAME_API_ROLE=tournament-worker node dist/index.js", "worker:tournament:dev": "GAME_API_ROLE=tournament-worker tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api --watch", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target app/game-api", + "lint:fix": "pnpm -w lint:target:fix app/game-api", "test": "vitest run --config vitest.config.ts", "typecheck": "pnpm -w tsc7 -b app/game-api/tsconfig.json" }, diff --git a/app/game-engine/package.json b/app/game-engine/package.json index f2ed4b63..2201ab88 100644 --- a/app/game-engine/package.json +++ b/app/game-engine/package.json @@ -103,8 +103,8 @@ "build": "tsdown -c ./tsdown.config.ts && node ../../tools/check-package-exports.mjs .", "dev": "tsdown -c ./tsdown.config.ts --watch", "start": "pnpm run build && GAME_ENGINE_ROLE=turn-daemon node dist/index.js", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target app/game-engine", + "lint:fix": "pnpm -w lint:target:fix app/game-engine", "profile:npc-unification-memory": "node scripts/profile-npc-unification-memory.mjs", "profile:npc-unification-timing": "node scripts/profile-npc-unification-timing.mjs", "profile:npc-lifecycle-memory": "node scripts/profile-npc-lifecycle-memory.mjs", diff --git a/app/game-frontend/package.json b/app/game-frontend/package.json index 8e851b51..e988f539 100644 --- a/app/game-frontend/package.json +++ b/app/game-frontend/package.json @@ -28,8 +28,8 @@ "test:e2e:npc-possession-live": "pnpm --filter @sammo-ts/infra prisma:generate && pnpm --filter @sammo-ts/common build && pnpm --filter @sammo-ts/logic build && pnpm --filter @sammo-ts/infra build && pnpm --filter @sammo-ts/game-engine build && pnpm --filter @sammo-ts/game-api build && playwright test --config e2e/npcPossession.live.playwright.config.mjs --tsconfig e2e/playwright.live.tsconfig.json", "test:e2e:die-on-prestart-live": "pnpm --filter @sammo-ts/infra prisma:generate && pnpm --filter @sammo-ts/common build && pnpm --filter @sammo-ts/logic build && pnpm --filter @sammo-ts/infra build && pnpm --filter @sammo-ts/game-engine build && pnpm --filter @sammo-ts/game-api build && playwright test --config e2e/dieOnPrestart.live.playwright.config.mjs --tsconfig e2e/playwright.live.tsconfig.json", "test:e2e:default-recruit-commands-live": "pnpm --filter @sammo-ts/infra prisma:generate && pnpm --filter @sammo-ts/common build && pnpm --filter @sammo-ts/logic build && pnpm --filter @sammo-ts/infra build && pnpm --filter @sammo-ts/game-engine build && pnpm --filter @sammo-ts/game-api build && playwright test --config e2e/defaultRecruitCommands.live.playwright.config.mjs --tsconfig e2e/playwright.live.tsconfig.json", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target app/game-frontend", + "lint:fix": "pnpm -w lint:target:fix app/game-frontend", "typecheck": "vue-tsc --noEmit" }, "dependencies": { diff --git a/app/gateway-api/package.json b/app/gateway-api/package.json index cb27ef64..9aee8cd7 100644 --- a/app/gateway-api/package.json +++ b/app/gateway-api/package.json @@ -18,8 +18,8 @@ "build": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/gateway-api", "dev": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/gateway-api --watch", "orchestrator": "GATEWAY_ROLE=orchestrator node dist/index.js", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target app/gateway-api", + "lint:fix": "pnpm -w lint:target:fix app/gateway-api", "test": "vitest run --config vitest.config.ts", "typecheck": "pnpm -w tsc7 -b app/gateway-api/tsconfig.json" }, diff --git a/app/gateway-frontend/package.json b/app/gateway-frontend/package.json index 2db93970..75cfa170 100644 --- a/app/gateway-frontend/package.json +++ b/app/gateway-frontend/package.json @@ -13,8 +13,8 @@ "build:release": "vite build", "typecheck:release": "vue-tsc --noEmit", "preview": "vite preview", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target app/gateway-frontend", + "lint:fix": "pnpm -w lint:target:fix app/gateway-frontend", "typecheck": "vue-tsc --noEmit" }, "dependencies": { diff --git a/app/release-controller/package.json b/app/release-controller/package.json index 8e6e1a73..9ca57dd1 100644 --- a/app/release-controller/package.json +++ b/app/release-controller/package.json @@ -11,8 +11,8 @@ "run-once": "node dist/index.js run-once", "status": "node dist/index.js status", "self-upgrade": "node dist/index.js self-upgrade", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target app/release-controller", + "lint:fix": "pnpm -w lint:target:fix app/release-controller", "test": "vitest run --config vitest.config.ts", "typecheck": "pnpm -w tsc7 -b app/release-controller/tsconfig.json" }, diff --git a/docs/testing-policy.md b/docs/testing-policy.md index 9d031fcd..824e5c6a 100644 --- a/docs/testing-policy.md +++ b/docs/testing-policy.md @@ -19,6 +19,11 @@ pnpm test pnpm build ``` +루트 lint는 ESLint의 일반 규칙과 Vue 규칙을 먼저 실행하고, type-aware Promise +규칙은 TypeScript 파일에 Oxlint, Vue SFC에 ESLint를 사용합니다. 전체 cold 경로를 +확인할 때는 `pnpm lint:uncached`, package 하나는 +`pnpm --filter lint`를 사용합니다. + 루트 `pnpm test`와 `pnpm build`는 간단 출력이 기본입니다. Turbo는 실패한 task의 로그를 남기고 성공 task의 본문을 생략하며, Vitest를 package에서 직접 실행할 때도 `minimal` reporter를 사용합니다. 테스트별 진행과 성공 task 로그까지 필요한 진단은 diff --git a/eslint.config.js b/eslint.config.js index f0886be4..82135566 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,12 +8,15 @@ import globals from 'globals'; // projectService reloads every TS project when this value changes between TS and Vue files. const extraFileExtensions = ['.vue']; -const typedParserOptions = { - extraFileExtensions, - jsDocParsingMode: 'none', - projectService: true, - tsconfigRootDir: import.meta.dirname, -}; +const typeAware = process.env.ESLINT_TYPE_AWARE !== '0'; +const typedParserOptions = typeAware + ? { + extraFileExtensions, + jsDocParsingMode: 'none', + projectService: true, + tsconfigRootDir: import.meta.dirname, + } + : {}; export default tseslint.config( { @@ -68,6 +71,8 @@ export default tseslint.config( { files: ['**/*.{ts,tsx,vue}'], rules: { + // TypeScript resolves ambient and imported names more accurately than ESLint's base rule. + 'no-undef': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { @@ -84,9 +89,9 @@ export default tseslint.config( fixStyle: 'separate-type-imports', }, ], - '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-floating-promises': typeAware ? 'error' : 'off', '@typescript-eslint/no-misused-promises': [ - 'error', + typeAware ? 'error' : 'off', { checksVoidReturn: { arguments: false, @@ -110,8 +115,7 @@ export default tseslint.config( { object: 'Math', property: 'random', - message: - 'Authoritative game state must use an explicitly seeded RandUtil/LiteHashDRBG stream.', + message: 'Authoritative game state must use an explicitly seeded RandUtil/LiteHashDRBG stream.', }, ], }, diff --git a/eslint.promise.config.js b/eslint.promise.config.js new file mode 100644 index 00000000..03451ba9 --- /dev/null +++ b/eslint.promise.config.js @@ -0,0 +1,65 @@ +import tseslint from 'typescript-eslint'; +import vueParser from 'vue-eslint-parser'; + +const project = process.env.ESLINT_TYPED_PROJECT; +if (!project) { + throw new Error('ESLINT_TYPED_PROJECT must point to the target tsconfig.json'); +} + +const typedParserOptions = { + extraFileExtensions: ['.vue'], + jsDocParsingMode: 'none', + project: [project], + tsconfigRootDir: import.meta.dirname, +}; +const promiseRules = { + '@typescript-eslint/no-floating-promises': 'error', + '@typescript-eslint/no-misused-promises': [ + 'error', + { + checksVoidReturn: { + arguments: false, + attributes: false, + }, + }, + ], +}; +const plugins = { + '@typescript-eslint': tseslint.plugin, +}; + +export default tseslint.config( + { + ignores: [ + '**/dist/**', + '**/node_modules/**', + '**/*.d.ts', + 'legacy/**', + 'packages/infra/prisma/client/**', + '**/generated/**', + '.pnpm-store/**', + ], + }, + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + parser: tseslint.parser, + parserOptions: typedParserOptions, + }, + plugins, + rules: promiseRules, + }, + { + files: ['**/*.vue'], + languageOptions: { + parser: vueParser, + parserOptions: { + ...typedParserOptions, + parser: tseslint.parser, + sourceType: 'module', + }, + }, + plugins, + rules: promiseRules, + } +); diff --git a/package.json b/package.json index 1c4a1934..54611a3b 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,11 @@ "version": "0.0.0", "packageManager": "pnpm@11.21.0", "scripts": { - "lint": "NODE_OPTIONS=--max-old-space-size=1024 turbo lint --concurrency=1", - "lint:fix": "NODE_OPTIONS=--max-old-space-size=1024 turbo lint:fix --concurrency=1", + "lint": "node tools/run-lint.mjs", + "lint:fix": "node tools/run-lint.mjs --fix", + "lint:uncached": "node tools/run-lint.mjs --no-cache", + "lint:target": "node tools/run-lint.mjs --target", + "lint:target:fix": "node tools/run-lint.mjs --fix --target", "format": "prettier --write .", "test": "turbo test --ui=stream --output-logs=errors-only", "test-verbose": "SAMMO_TEST_REPORTER=verbose turbo test --ui=stream --output-logs=full", @@ -53,6 +56,8 @@ "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-vue": "^10.10.0", "globals": "^17.11.0", + "oxlint": "1.80.0", + "oxlint-tsgolint": "7.0.2001", "prettier": "^3.9.6", "tsdown": "^0.22.14", "turbo": "^2.10.9", diff --git a/packages/common/package.json b/packages/common/package.json index 19308bd0..f83f87a0 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -62,8 +62,8 @@ "scripts": { "build": "tsdown -c ./tsdown.config.ts", "dev": "tsdown -c ./tsdown.config.ts --watch", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target packages/common", + "lint:fix": "pnpm -w lint:target:fix packages/common", "test": "vitest run --config vitest.config.ts", "typecheck": "pnpm -w tsc7 -b packages/common/tsconfig.json" }, diff --git a/packages/infra/package.json b/packages/infra/package.json index 669d8503..f5d5bee4 100644 --- a/packages/infra/package.json +++ b/packages/infra/package.json @@ -8,8 +8,8 @@ "scripts": { "build": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/infra", "dev": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/infra --watch", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target packages/infra", + "lint:fix": "pnpm -w lint:target:fix packages/infra", "test": "vitest run --config vitest.config.ts", "prisma:generate": "pnpm prisma:generate:game && pnpm prisma:generate:gateway", "typecheck": "pnpm -w tsc7 -b packages/infra/tsconfig.json", diff --git a/packages/logic/package.json b/packages/logic/package.json index 29ed782f..46632944 100644 --- a/packages/logic/package.json +++ b/packages/logic/package.json @@ -15,8 +15,8 @@ "scripts": { "build": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/logic", "dev": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/logic --watch", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target packages/logic", + "lint:fix": "pnpm -w lint:target:fix packages/logic", "test": "vitest run --config vitest.config.ts", "typecheck": "pnpm -w tsc7 -b packages/logic/tsconfig.json" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7a345b0..eca43567 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,6 +49,12 @@ importers: globals: specifier: ^17.11.0 version: 17.11.0 + oxlint: + specifier: 1.80.0 + version: 1.80.0(oxlint-tsgolint@7.0.2001) + oxlint-tsgolint: + specifier: 7.0.2001 + version: 7.0.2001 prettier: specifier: ^3.9.6 version: 3.9.6 @@ -1335,6 +1341,158 @@ packages: '@oxc-project/types@0.144.0': resolution: {integrity: sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==} + '@oxlint-tsgolint/darwin-arm64@7.0.2001': + resolution: {integrity: sha512-CUJEdbSZ54+Xy9OXqOhWLTKZKV0BBiV7C2i/ygyVmXtkUNXx5YCzN8DpSSshTAKktoL7S+tnQ/ftFG/i7X896w==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2001': + resolution: {integrity: sha512-pXfBb5BqONCcgrXQNUZWXgiYmRSWJzd97S8i41VVOh6ut0tyo+cJ5FKFpczDHxiVNfj/3e7c9B4MtztNdpIVCw==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@7.0.2001': + resolution: {integrity: sha512-roP7zujb/QDPzDwEKsFFpzNHHy91/Y7oX9vQXk78ekyZtcQj1QXDIMH33gjDdHBfRl4K9pZ36xhRgrP4Zr+R8A==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2001': + resolution: {integrity: sha512-UDezNqdECVmngu2TPnjaS1YoAmcTaBoI5lV9vk3VahBxoi+I5r9k3iJTT7qZoYWOXTD/7T7bNcwRgrocR6BscQ==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2001': + resolution: {integrity: sha512-uJZhqB6pdXLuN+AD1F5082byyQti/NPmJA77GtcFlmT2HzRelqbNls3SaIqxpjdFgvSBF9g0yOKGBkGFg7kX8Q==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@7.0.2001': + resolution: {integrity: sha512-FkDRm8hx9OwzGQqyWG1tO5QrTLRApff9DzSgpz9QZau37BR8d1VYKOxMLGf6shPZntJFoTwIIJYT68VndYDCog==} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.80.0': + resolution: {integrity: sha512-RM3Plj+biQpxa5d1GOOX6ciDlcUROmm4OZ/pLTpitkQt2mJv4jhtY4cbgaetOm5UKWZe05/TGQ6o1Vl8EOHkrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.80.0': + resolution: {integrity: sha512-YlO5JEf0Yr2bUUlu8O8daVcUxtcGGbcSmyV7E7nSbJbfAdxTE0PFPwgnIlw7wXJaTYjb+qs5hI5q3jxUkI7cAw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.80.0': + resolution: {integrity: sha512-BULDOyO3AhsmdWfQeIUCykDt3dd7XZBGLhp1eIh56skRv01O+cNjNPwXMIbeW1x4+pxcln5if72wcRgViVo7PA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.80.0': + resolution: {integrity: sha512-YJ4JzLw7N5TDSQFlA0hAQGHvnDZgyypm1yunObVWcWiF9KM7eGCJKYKLgTC2Fi/57OdnBhbj4OkzPGdFQJ6HyA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.80.0': + resolution: {integrity: sha512-AYUIk5QnL0s8oWAYsREZwkRYy1SupJTXALo93J1TgzHywxQtdM99FecRMQ87MXEdPQ0j1TmEpeeq3fGNkpvMqg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.80.0': + resolution: {integrity: sha512-9hBZVANupQ89W9dXyE0n8doCyaW5pDyGn3y6XlIMPZ+rIKuyqkr3SNUXmVJIhuvUq0NBU3RBiSXXE69l4XI6KA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.80.0': + resolution: {integrity: sha512-SvS2uKqzY+pbfuvAHzH4338R6Zwo805GAwrIMVvK1KxoOWCIjZUdfzTCvilD7z6JK91v011+zYMryabhDo2AsQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.80.0': + resolution: {integrity: sha512-tCLadyqRVL3pQTRPNg7cjXKvcvS4fbyXeQHhKk5BTJ1oftQln5/yIIWbu/Xom/DX41zv2P9QGt6+D/TtQVtY3A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.80.0': + resolution: {integrity: sha512-XfpCNRlOPcLlJl4Bn/FUhjqlR6BVavEykERBf/MV7YA9VZDa5g5znVqYhyviMafcxS9Pe/i/kPvHNO0U6svEHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.80.0': + resolution: {integrity: sha512-3I4yMwcFG9NeO8ioY6JBBuKsIm5GL/x7MATt1S4tVWaxPu5HcJ+XnLUbcVBTxG8q2Wu56HSj+NmXQiVYb1lp6A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.80.0': + resolution: {integrity: sha512-E1wAKymkpe1/E8helzBKdm81OBOF+ezxRyXRMEuik3ZpWDER5CPOKZwF66RsdwW98uwZv8UTFremUQtC1CzdJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.80.0': + resolution: {integrity: sha512-+gLRGD4sIo3+VA++iham5UxD9tKSoJ/VOrROCEXIcknrYtQg6iIQgvjN0cpiRF7N6UYC7pJbvHJlDnMge5LRpQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.80.0': + resolution: {integrity: sha512-aR0PrzHj9leW3NmzBAAP4EzdoBNoJcs9sjnIQPIwyRnBGYrRbXUIpEB5Q39AqK3PLY5JK5uEhDQDiUa1QSAstw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.80.0': + resolution: {integrity: sha512-vSVh5cSo3Xxs6ghBCcFJlpbkbENzDog1qXtoXLa/HC3aCrR4XO76GZbXmQoCPHnu99nQpdCeC3H9tdNICfDh7A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.80.0': + resolution: {integrity: sha512-FfzBXpNQ8u7/ZI/p8bl73MeZ508Ax3hxWp3SiJpEFiC+BB9XcXy5FAZHTLKDPSzrUpxQZSZJAVdDmuJp/+HDBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.80.0': + resolution: {integrity: sha512-zMzbkumtmprCgRwoYNzcB3iC39fXdJIMLMU33KdCjEGLlJGOEt1+LwQ4LF8ndLzAEKVz4BR0y3V6Xrkk3Nm3yA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.80.0': + resolution: {integrity: sha512-ib6iRcrXsk4t1fm3iKcwksyWh1ZkZXC/2mEzakl0ai2+6HZunf1WWMZ/xP9EJAvw9g9K4UVTC3NF/+G2qLrbTQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxlint/binding-win32-ia32-msvc@1.80.0': + resolution: {integrity: sha512-xhRWBMpLxZvgKAH6+DJZmpP+W8Y8UdQOSU1JfxSWNXsaBaRGW77j+1hCuNHlzj7OH4SPN8fYd1q0o2qrDtoVyw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxlint/binding-win32-x64-msvc@1.80.0': + resolution: {integrity: sha512-yAnO7lwBYQnz2pcfBPIGQQZWIX5zd5R/1aAKIF3oE+TVj7IhoHcROjOkz3sRDngzqhfPKfFaXqug5j5rE5dn6Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} @@ -3977,6 +4135,23 @@ packages: orderedmap@2.1.1: resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} + oxlint-tsgolint@7.0.2001: + resolution: {integrity: sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==} + hasBin: true + + oxlint@1.80.0: + resolution: {integrity: sha512-5nTiSps4qdbCWLbxzuO00alHkEO2exR9YMN/ig6QXWrLsYSG0KaObOAM+l6oU2LcKPWoSAGYbkZIGEu1ViiWKA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=7.0.2001' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -5522,6 +5697,81 @@ snapshots: '@oxc-project/types@0.144.0': {} + '@oxlint-tsgolint/darwin-arm64@7.0.2001': + optional: true + + '@oxlint-tsgolint/darwin-x64@7.0.2001': + optional: true + + '@oxlint-tsgolint/linux-arm64@7.0.2001': + optional: true + + '@oxlint-tsgolint/linux-x64@7.0.2001': + optional: true + + '@oxlint-tsgolint/win32-arm64@7.0.2001': + optional: true + + '@oxlint-tsgolint/win32-x64@7.0.2001': + optional: true + + '@oxlint/binding-android-arm-eabi@1.80.0': + optional: true + + '@oxlint/binding-android-arm64@1.80.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.80.0': + optional: true + + '@oxlint/binding-darwin-x64@1.80.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.80.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.80.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.80.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.80.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.80.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.80.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.80.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.80.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.80.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.80.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.80.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.80.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.80.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.80.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.80.0': + optional: true + '@pinojs/redact@0.4.0': {} '@pkgr/core@0.3.6': {} @@ -7952,6 +8202,38 @@ snapshots: orderedmap@2.1.1: {} + oxlint-tsgolint@7.0.2001: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2001 + '@oxlint-tsgolint/darwin-x64': 7.0.2001 + '@oxlint-tsgolint/linux-arm64': 7.0.2001 + '@oxlint-tsgolint/linux-x64': 7.0.2001 + '@oxlint-tsgolint/win32-arm64': 7.0.2001 + '@oxlint-tsgolint/win32-x64': 7.0.2001 + + oxlint@1.80.0(oxlint-tsgolint@7.0.2001): + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.80.0 + '@oxlint/binding-android-arm64': 1.80.0 + '@oxlint/binding-darwin-arm64': 1.80.0 + '@oxlint/binding-darwin-x64': 1.80.0 + '@oxlint/binding-freebsd-x64': 1.80.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.80.0 + '@oxlint/binding-linux-arm-musleabihf': 1.80.0 + '@oxlint/binding-linux-arm64-gnu': 1.80.0 + '@oxlint/binding-linux-arm64-musl': 1.80.0 + '@oxlint/binding-linux-ppc64-gnu': 1.80.0 + '@oxlint/binding-linux-riscv64-gnu': 1.80.0 + '@oxlint/binding-linux-riscv64-musl': 1.80.0 + '@oxlint/binding-linux-s390x-gnu': 1.80.0 + '@oxlint/binding-linux-x64-gnu': 1.80.0 + '@oxlint/binding-linux-x64-musl': 1.80.0 + '@oxlint/binding-openharmony-arm64': 1.80.0 + '@oxlint/binding-win32-arm64-msvc': 1.80.0 + '@oxlint/binding-win32-ia32-msvc': 1.80.0 + '@oxlint/binding-win32-x64-msvc': 1.80.0 + oxlint-tsgolint: 7.0.2001 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 diff --git a/tools/legacy-db-migration/package.json b/tools/legacy-db-migration/package.json index c78409d4..fc9a3008 100644 --- a/tools/legacy-db-migration/package.json +++ b/tools/legacy-db-migration/package.json @@ -8,8 +8,8 @@ }, "scripts": { "build": "tsdown src/cli.ts --no-config --format esm --out-dir dist --dts --sourcemap --target node22 --platform node", - "lint": "eslint .", - "lint:fix": "eslint . --fix", + "lint": "pnpm -w lint:target tools/legacy-db-migration", + "lint:fix": "pnpm -w lint:target:fix tools/legacy-db-migration", "test": "vitest run --config vitest.config.ts", "typecheck": "pnpm -w tsc7 -b tools/legacy-db-migration/tsconfig.json", "migrate": "tsx src/cli.ts" diff --git a/tools/run-lint.mjs b/tools/run-lint.mjs new file mode 100644 index 00000000..c2e42d4b --- /dev/null +++ b/tools/run-lint.mjs @@ -0,0 +1,98 @@ +import { spawnSync } from 'node:child_process'; + +const allLintTargets = [ + 'app/game-api', + 'app/game-engine', + 'app/game-frontend', + 'app/gateway-api', + 'app/gateway-frontend', + 'app/release-controller', + 'packages/common', + 'packages/infra', + 'packages/logic', + 'tools/legacy-db-migration', +]; +const targetArgumentIndex = process.argv.indexOf('--target'); +const requestedTarget = targetArgumentIndex === -1 ? undefined : process.argv[targetArgumentIndex + 1]; +if (targetArgumentIndex !== -1 && !requestedTarget) { + console.error('Usage: run-lint.mjs --target '); + process.exit(2); +} +if (requestedTarget && !allLintTargets.includes(requestedTarget)) { + console.error(`Unknown lint target: ${requestedTarget}`); + process.exit(2); +} +const lintTargets = requestedTarget ? [requestedTarget] : allLintTargets; +const eslintTargets = [ + ...lintTargets, + ...(!requestedTarget ? ['eslint.config.js', 'eslint.promise.config.js', 'tools/run-lint.mjs'] : []), +]; +const vueTargets = ['app/game-frontend', 'app/gateway-frontend'] + .filter((target) => lintTargets.includes(target)) + .map((target) => ({ glob: `${target}/**/*.vue`, project: `${target}/tsconfig.json`, target })); +const shouldFix = process.argv.includes('--fix'); +const useCache = !process.argv.includes('--no-cache'); +const pnpmCommand = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'; + +const withNodeHeap = (heapSizeMb, extraEnv = {}) => { + const inheritedOptions = (process.env.NODE_OPTIONS ?? '') + .replace(/--max[-_]old[-_]space[-_]size(?:=|\s+)\d+/g, '') + .trim(); + return { + ...process.env, + ...extraEnv, + NODE_OPTIONS: `${inheritedOptions} --max-old-space-size=${heapSizeMb}`.trim(), + }; +}; + +const run = (label, args, env = process.env) => { + console.log(`\n[lint] ${label}`); + const result = spawnSync(pnpmCommand, ['exec', ...args], { + env, + stdio: 'inherit', + }); + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + process.exit(result.status ?? 1); + } +}; + +run( + 'ESLint syntax and Vue rules', + [ + 'eslint', + ...(shouldFix ? ['--fix'] : []), + ...(useCache + ? ['--cache', '--cache-strategy', 'content', '--cache-location', 'node_modules/.cache/eslint/syntax'] + : []), + ...eslintTargets, + ], + withNodeHeap(512, { ESLINT_TYPE_AWARE: '0' }) +); +for (const lintTarget of lintTargets) { + run(`Oxlint type-aware TypeScript promise rules: ${lintTarget}`, ['oxlint', lintTarget]); +} +for (const vueTarget of vueTargets) { + const cacheName = vueTarget.target.split('/')[1]; + run( + `ESLint type-aware Vue promise rules: ${vueTarget.glob}`, + [ + 'eslint', + '--config', + 'eslint.promise.config.js', + ...(useCache + ? [ + '--cache', + '--cache-strategy', + 'content', + '--cache-location', + `node_modules/.cache/eslint/${cacheName}-vue-promise`, + ] + : []), + vueTarget.glob, + ], + withNodeHeap(1280, { ESLINT_TYPED_PROJECT: vueTarget.project }) + ); +}