diff --git a/.github/rulesets/main-branch-protection-with-conventional-commits.json b/.github/rulesets/main-branch-protection-with-conventional-commits.json deleted file mode 100644 index a983c91..0000000 --- a/.github/rulesets/main-branch-protection-with-conventional-commits.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "Main Branch Protection (with Conventional Commits)", - "target": "branch", - "enforcement": "active", - "bypass_actors": [ - { - "actor_id": 5, - "actor_type": "RepositoryRole", - "bypass_mode": "pull_request" - } - ], - "conditions": { - "ref_name": { - "include": ["refs/heads/main"], - "exclude": [] - } - }, - "rules": [ - { - "type": "update", - "parameters": { - "update_allows_fetch_and_merge": true - } - }, - { - "type": "deletion" - }, - { - "type": "pull_request", - "parameters": { - "required_approving_review_count": 1, - "dismiss_stale_reviews_on_push": true, - "require_code_owner_review": false, - "require_last_push_approval": true, - "required_review_thread_resolution": true - } - }, - { - "type": "required_status_checks", - "parameters": { - "strict_required_status_checks_policy": true, - "required_status_checks": [ - { - "context": "Publish", - "integration_id": null - } - ] - } - }, - { - "type": "commit_message_pattern", - "parameters": { - "name": "Conventional Commits", - "negate": false, - "operator": "starts_with", - "pattern": "^(feat|fix|docs|style|refactor|test|chore|perf|ci|revert)(\\(.+\\))?:.+" - } - } - ] -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..1e1568c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI + +on: + pull_request: + branches: + - main + +jobs: + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install + run: bun install --frozen-lockfile + + - name: Lint + run: bun run lint + + - name: Format check + run: bun run format:check + + - name: Type check + run: bun run typecheck + + - name: Test + run: bun test + + - name: Build + run: bun run build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4950290..dcad9a2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -51,9 +51,9 @@ jobs: if: steps.npm.outputs.exists == 'false' && success() run: npm ci - - name: Test + - name: Build if: steps.npm.outputs.exists == 'false' && success() - run: npm test + run: npm run build - name: Publish if: steps.npm.outputs.exists == 'false' && success() diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..65eca4e --- /dev/null +++ b/biome.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noExplicitAny": "off" + }, + "style": { + "noNonNullAssertion": "off" + }, + "performance": { + "noDelete": "off" + } + } + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 80 + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "asNeeded", + "trailingCommas": "all" + } + }, + "files": { + "ignore": ["dist", "node_modules", "coverage", "*.md"] + } +} diff --git a/package-lock.json b/package-lock.json index 2b8456d..4e9b6d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "opencode-discord-notify", - "version": "0.7.0", + "version": "0.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "opencode-discord-notify", - "version": "0.7.0", + "version": "0.10.0", "license": "MIT", "devDependencies": { + "@biomejs/biome": "^1.9.4", "@opencode-ai/plugin": "1.0.194", "@types/node": "^20.19.27", "@vitest/coverage-v8": "^4.0.16", - "prettier": "^3.7.4", - "prettier-plugin-organize-imports": "^4.3.0", + "bun-types": "^1.3.6", "tsup": "^8.5.0", "typescript": "^5.8.3", "vitest": "^4.0.16" @@ -85,6 +85,170 @@ "node": ">=18" } }, + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", @@ -1118,6 +1282,16 @@ "js-tokens": "^9.0.1" } }, + "node_modules/bun-types": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.6.tgz", + "integrity": "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -1681,40 +1855,6 @@ } } }, - "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-organize-imports": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.3.0.tgz", - "integrity": "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": ">=2.0", - "typescript": ">=2.9", - "vue-tsc": "^2.1.0 || 3" - }, - "peerDependenciesMeta": { - "vue-tsc": { - "optional": true - } - } - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", diff --git a/package.json b/package.json index 8fd2ab4..5f6b234 100644 --- a/package.json +++ b/package.json @@ -15,18 +15,16 @@ "default": "./dist/index.js" } }, - "files": [ - "dist", - "README.md", - "README-JP.md", - "LICENSE" - ], + "files": ["dist", "README.md", "README-JP.md", "LICENSE"], "publishConfig": { "access": "public" }, "scripts": { "build": "tsup src/index.ts --format esm --dts --out-dir dist --clean --external bun:sqlite", - "format": "prettier . --write", + "lint": "biome lint .", + "format": "biome format --write .", + "format:check": "biome check .", + "typecheck": "tsc --noEmit", "test": "bun test", "prepublishOnly": "npm run build" }, @@ -34,21 +32,16 @@ "@opencode-ai/plugin": ">=1.0.0" }, "devDependencies": { + "@biomejs/biome": "^1.9.4", "@opencode-ai/plugin": "1.0.194", "@types/node": "^20.19.27", "@vitest/coverage-v8": "^4.0.16", - "prettier": "^3.7.4", - "prettier-plugin-organize-imports": "^4.3.0", + "bun-types": "^1.3.6", "tsup": "^8.5.0", "typescript": "^5.8.3", "vitest": "^4.0.16" }, - "keywords": [ - "opencode", - "discord", - "webhook", - "plugin" - ], + "keywords": ["opencode", "discord", "webhook", "plugin"], "repository": { "type": "git", "url": "https://github.com/j4rviscmd/opencode-discord-notify" diff --git a/src/index.test.ts b/src/index.test.ts index c447883..e752e2e 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -25,8 +25,8 @@ async function waitForQueueWorker(instance: any, timeout = 5000) { describe('__test__.toIsoTimestamp', () => { it('non-number or non-finite returns undefined', () => { expect(__test__.toIsoTimestamp('1')).toBeUndefined() - expect(__test__.toIsoTimestamp(NaN)).toBeUndefined() - expect(__test__.toIsoTimestamp(Infinity)).toBeUndefined() + expect(__test__.toIsoTimestamp(Number.NaN)).toBeUndefined() + expect(__test__.toIsoTimestamp(Number.POSITIVE_INFINITY)).toBeUndefined() }) it('returns ISO string for numeric input', () => { diff --git a/src/index.ts b/src/index.ts index 0501036..0bc2bfb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -210,7 +210,7 @@ function sleep(ms: number): Promise { function truncateText(value: string, maxLength: number): string { if (value.length <= maxLength) return value if (maxLength <= 3) return value.slice(0, maxLength) - return value.slice(0, maxLength - 3) + '...' + return `${value.slice(0, maxLength - 3)}...` } function buildMention( @@ -1052,9 +1052,7 @@ const plugin: Plugin = async ({ client }) => { const mention = buildCompleteMention() const body: DiscordExecuteWebhookBody = { - content: mention - ? `${mention.content} Session error` - : undefined, + content: mention ? `${mention.content} Session error` : undefined, allowed_mentions: mention?.allowed_mentions, embeds: [embed], } @@ -1173,7 +1171,6 @@ const plugin: Plugin = async ({ client }) => { }, } } - ;(plugin as any).__test__ = { buildMention, buildTodoChecklist, diff --git a/src/queue/worker.test.ts b/src/queue/worker.test.ts index 36a824d..f84608c 100644 --- a/src/queue/worker.test.ts +++ b/src/queue/worker.test.ts @@ -57,6 +57,7 @@ describe('QueueWorker', () => { postDeps: {}, maybeAlertError: vi.fn(), webhookUrl: 'https://example.com/webhook', + buildThreadName: vi.fn((sessionId) => `thread-${sessionId}`), }) // 1つ目のstartを開始(バックグラウンドで実行) @@ -99,6 +100,7 @@ describe('QueueWorker', () => { postDeps: {}, maybeAlertError: vi.fn(), webhookUrl: 'https://example.com/webhook', + buildThreadName: vi.fn((sessionId) => `thread-${sessionId}`), }) const startPromise = worker.start() @@ -168,6 +170,7 @@ describe('QueueWorker', () => { webhookUrl: 'https://example.com/webhook', username: 'TestBot', avatarUrl: 'https://example.com/avatar.png', + buildThreadName: vi.fn((sessionId) => `thread-${sessionId}`), }) await worker.start() @@ -276,6 +279,7 @@ describe('QueueWorker', () => { postDeps: {}, maybeAlertError: mockMaybeAlertError, webhookUrl: 'https://example.com/webhook', + buildThreadName: vi.fn((sessionId) => `thread-${sessionId}`), }) await worker.start() @@ -332,6 +336,7 @@ describe('QueueWorker', () => { postDeps: {}, maybeAlertError: mockMaybeAlertError, webhookUrl: 'https://example.com/webhook', + buildThreadName: vi.fn((sessionId) => `thread-${sessionId}`), }) await worker.start() @@ -391,6 +396,7 @@ describe('QueueWorker', () => { postDeps: {}, maybeAlertError: vi.fn(), webhookUrl: 'https://example.com/webhook', + buildThreadName: vi.fn((sessionId) => `thread-${sessionId}`), }) await worker.start() diff --git a/src/utils/db.ts b/src/utils/db.ts index 96dc15d..af326ba 100644 --- a/src/utils/db.ts +++ b/src/utils/db.ts @@ -1,7 +1,7 @@ import { Database } from 'bun:sqlite' -import fs from 'fs' -import os from 'os' -import path from 'path' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' export function getDbPath(): string { // テスト環境ではin-memory DBを使用 diff --git a/tsconfig.json b/tsconfig.json index b473bf6..f34ca22 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "esModuleInterop": true, "resolveJsonModule": true, "forceConsistentCasingInFileNames": true, - "types": ["node"] + "types": ["bun-types", "node"] }, - "include": ["src/**/*.ts"] + "include": ["src/**/*.ts"], + "exclude": ["src/plugin/internals.ts"] }