diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a3cef6c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - master + +jobs: + validate: + runs-on: macos-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Test + run: npm run test + + - name: Verify assets + run: npm run verify:assets + + - name: Verify dependencies + run: npm run verify:deps + + - name: Build + run: npm run build diff --git a/CHANGELOG.md b/CHANGELOG.md index 529493d..7cc2499 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ # crickets Changelog +## [Stability and CI hardening] - {PR_MERGE_DATE} + +- Refactor command runtime flow to use deterministic bundled asset resolution. +- Add explicit failure handling for missing/unreadable sound assets. +- Remove unused runtime dependencies to keep the extension lightweight. +- Add minimal automated tests for path resolution and command behavior. +- Add CI workflow for lint, test, verification, and build checks. +- Refresh README with current command behavior and requirements. + ## [Initial Version] - {PR_MERGE_DATE} \ No newline at end of file diff --git a/README.md b/README.md index 35aff17..95aff11 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,28 @@ -# crickets +# Crickets -Plays a Crickets sound for use in weird situations where you need to quickly play a crickets sound \ No newline at end of file +Play a quick cricket chirp sound effect from Raycast. + +## Features + +- Single `no-view` command for instant playback +- Bundled audio asset (`assets/crickets.mp3`) for deterministic behavior +- Fast failure feedback when playback cannot start +- macOS-only support (uses `afplay`) + +## Command + +- `Crickets`: plays the bundled cricket sound and shows a HUD confirmation + +## Requirements + +- Raycast +- macOS + +## Development + +```bash +npm install +npm run check +``` + +`npm run check` runs linting, tests, asset/dependency verification, and a production build to stay aligned with Raycast publication checks. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dde0c90..016474c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,17 +7,15 @@ "name": "crickets", "license": "MIT", "dependencies": { - "@raycast/api": "^1.91.0", - "@raycast/utils": "^1.17.0", - "play-sound": "^1.1.6" + "@raycast/api": "^1.91.0" }, "devDependencies": { "@raycast/eslint-config": "^1.0.11", "@types/node": "20.8.10", - "@types/play-sound": "^1.1.2", "@types/react": "18.3.3", "eslint": "^8.57.0", "prettier": "^3.3.3", + "tsx": "^4.21.0", "typescript": "^5.4.5" } }, @@ -357,6 +355,23 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", @@ -1064,23 +1079,6 @@ "eslint": ">=7" } }, - "node_modules/@raycast/utils": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/@raycast/utils/-/utils-1.18.1.tgz", - "integrity": "sha512-fNrybWovB5WSiotqrExMNVWtODH5DATFMvqJboIjwM2X8Ddvgt7tkf2Ol0vA0UBDVaGwDV+jpX/ZBhMnjz5TzQ==", - "license": "MIT", - "dependencies": { - "cross-fetch": "^3.1.6", - "dequal": "^2.0.3", - "object-hash": "^3.0.0", - "signal-exit": "^4.0.2", - "stream-chain": "^2.2.5", - "stream-json": "^1.8.0" - }, - "peerDependencies": { - "@raycast/api": ">=1.69.0" - } - }, "node_modules/@rushstack/eslint-patch": { "version": "1.10.5", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.5.tgz", @@ -1104,16 +1102,6 @@ "undici-types": "~5.26.4" } }, - "node_modules/@types/play-sound": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/play-sound/-/play-sound-1.1.2.tgz", - "integrity": "sha512-alq2D5sBtVcq5bzL/yLr5SSQ7B4VTIkX728V3nYfo6TaCtnwFLdHsFgy+N9oCjh67OOZQnj7kkJauWNW0L8bWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -1730,15 +1718,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, - "node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1784,15 +1763,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2252,15 +2222,6 @@ "node": ">=8" } }, - "node_modules/find-exec": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/find-exec/-/find-exec-1.0.3.tgz", - "integrity": "sha512-gnG38zW90mS8hm5smNcrBnakPEt+cGJoiMkJwCU0IYnEb0H2NQk0NIljhNW+48oniCriFek/PH6QXbwsJo/qug==", - "license": "MIT", - "dependencies": { - "shell-quote": "^1.8.1" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2307,6 +2268,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -2316,6 +2292,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2809,35 +2798,6 @@ "dev": true, "license": "MIT" }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2978,15 +2938,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/play-sound": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/play-sound/-/play-sound-1.1.6.tgz", - "integrity": "sha512-09eO4QiXNFXJffJaOW5P6x6F5RLihpLUkXttvUZeWml0fU6x6Zp7AjG9zaeMpgH2ZNvq4GR1ytB22ddYcqJIZA==", - "license": "MIT", - "dependencies": { - "find-exec": "1.0.3" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3065,6 +3016,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -3156,18 +3117,6 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", - "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -3189,21 +3138,6 @@ "node": ">=8" } }, - "node_modules/stream-chain": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", - "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", - "license": "BSD-3-Clause" - }, - "node_modules/stream-json": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", - "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", - "license": "BSD-3-Clause", - "dependencies": { - "stream-chain": "^2.2.5" - } - }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -3289,12 +3223,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, "node_modules/ts-api-utils": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", @@ -3331,6 +3259,493 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3386,22 +3801,6 @@ "punycode": "^2.1.0" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 79457da..24115fa 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,12 @@ "description": "Plays a Crickets sound for use in weird situations where you need to quickly play a crickets sound", "icon": "extension-icon.png", "author": "kakaoduft", - "categories": ["Fun"], + "platforms": [ + "macOS" + ], + "categories": [ + "Fun" + ], "license": "MIT", "commands": [ { @@ -16,25 +21,27 @@ } ], "dependencies": { - "@raycast/api": "^1.91.0", - "@raycast/utils": "^1.17.0", - "play-sound": "^1.1.6" + "@raycast/api": "^1.91.0" }, "devDependencies": { "@raycast/eslint-config": "^1.0.11", "@types/node": "20.8.10", - "@types/play-sound": "^1.1.2", "@types/react": "18.3.3", "eslint": "^8.57.0", "prettier": "^3.3.3", + "tsx": "^4.21.0", "typescript": "^5.4.5" }, "scripts": { "build": "ray build", + "check": "npm run lint && npm run test && npm run verify:assets && npm run verify:deps && npm run build", "dev": "ray develop", "fix-lint": "ray lint --fix", "lint": "ray lint", "prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1", - "publish": "npx @raycast/api@latest publish" + "publish": "npx @raycast/api@latest publish", + "test": "node --test --import tsx test/*.test.ts", + "verify:assets": "node scripts/verify-assets.mjs", + "verify:deps": "node scripts/verify-deps.mjs" } } diff --git a/scripts/verify-assets.mjs b/scripts/verify-assets.mjs new file mode 100644 index 0000000..9ea977f --- /dev/null +++ b/scripts/verify-assets.mjs @@ -0,0 +1,21 @@ +import { existsSync, readFileSync } from "node:fs"; +import path from "node:path"; +import process from "node:process"; + +const rootDir = process.cwd(); +const packageJson = JSON.parse(readFileSync(path.join(rootDir, "package.json"), "utf8")); +const iconPath = packageJson.icon ? path.join("assets", packageJson.icon) : null; + +const requiredFiles = ["assets/crickets.mp3", "src/crickets.tsx", ...(iconPath ? [iconPath] : [])]; + +const missingFiles = requiredFiles.filter((relativePath) => !existsSync(path.join(rootDir, relativePath))); + +if (missingFiles.length > 0) { + console.error("Missing required extension files:"); + for (const file of missingFiles) { + console.error(`- ${file}`); + } + process.exit(1); +} + +console.log("Asset verification passed."); diff --git a/scripts/verify-deps.mjs b/scripts/verify-deps.mjs new file mode 100644 index 0000000..d40266b --- /dev/null +++ b/scripts/verify-deps.mjs @@ -0,0 +1,20 @@ +import process from "node:process"; +import fs from "node:fs"; +import path from "node:path"; + +const packageJsonPath = path.join(process.cwd(), "package.json"); +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); + +const dependencies = Object.keys(packageJson.dependencies ?? {}); +const allowedDependencies = new Set(["@raycast/api"]); +const extraDependencies = dependencies.filter((dependency) => !allowedDependencies.has(dependency)); + +if (extraDependencies.length > 0) { + console.error("Unexpected runtime dependencies detected:"); + for (const dependency of extraDependencies) { + console.error(`- ${dependency}`); + } + process.exit(1); +} + +console.log("Dependency verification passed."); diff --git a/src/crickets.tsx b/src/crickets.tsx index 03e7f29..33f43a7 100644 --- a/src/crickets.tsx +++ b/src/crickets.tsx @@ -1,34 +1,14 @@ -import { showHUD, showToast, Toast, getPreferenceValues } from "@raycast/api"; -import { exec } from "child_process"; -import path from "path"; - -// Get the correct extension root dynamically -const extensionPreferences = getPreferenceValues(); -const EXTENSION_NAME = extensionPreferences.extensionName || "crickets"; - -const BUNDLED_FILE = path.resolve(__dirname, "../assets/crickets.mp3"); // Development mode -const INSTALLED_FILE = path.join( - process.env.HOME || "", - `.config/raycast/extensions/${EXTENSION_NAME}/assets/crickets.mp3`, -); // Installed mode - -// Choose the correct file based on whether it's in development or installed -const SOUND_FILE = BUNDLED_FILE.includes("repos/raycast") ? BUNDLED_FILE : INSTALLED_FILE; +import { showHUD, showToast, Toast } from "@raycast/api"; +import { runCricketsCommand } from "./lib/play-crickets"; export default async function Command() { - try { - console.log("🔍 Resolved sound file path:", SOUND_FILE); - - exec(`afplay "${SOUND_FILE}"`, (error) => { - if (error) { - console.error("Error playing sound:", error); - showToast({ style: Toast.Style.Failure, title: "Failed to play sound", message: String(error) }); - } - }); - - await showHUD("🐛🎶 Playing cricket sounds... "); - } catch (error) { - console.error("Error:", error); - showToast({ style: Toast.Style.Failure, title: "Error", message: String(error) }); - } + await runCricketsCommand({ + showHud: (message) => showHUD(message), + showFailure: (title, message) => + showToast({ + style: Toast.Style.Failure, + title, + message, + }), + }); } diff --git a/src/lib/play-crickets.ts b/src/lib/play-crickets.ts new file mode 100644 index 0000000..3d87ffc --- /dev/null +++ b/src/lib/play-crickets.ts @@ -0,0 +1,57 @@ +import { spawn } from "node:child_process"; +import { constants } from "node:fs"; +import { access } from "node:fs/promises"; +import path from "node:path"; + +export const SOUND_ASSET_RELATIVE_PATH = "../assets/crickets.mp3"; +export const SUCCESS_HUD_MESSAGE = "Playing cricket sounds..."; + +type SpawnLike = typeof spawn; + +export interface RunCricketsCommandOptions { + baseDirectory?: string; + spawnImpl?: SpawnLike; + ensureReadable?: (filePath: string) => Promise; + showHud: (message: string) => Promise | void; + showFailure: (title: string, message: string) => Promise | void; +} + +export function resolveSoundFilePath(baseDirectory: string = __dirname): string { + return path.resolve(baseDirectory, SOUND_ASSET_RELATIVE_PATH); +} + +export async function ensureReadableFile(filePath: string): Promise { + await access(filePath, constants.R_OK); +} + +export async function playSoundFile(soundFilePath: string, spawnImpl: SpawnLike = spawn): Promise { + await new Promise((resolve, reject) => { + const child = spawnImpl("afplay", [soundFilePath], { stdio: "ignore" }); + child.once("error", reject); + child.once("spawn", () => resolve()); + }); +} + +export async function runCricketsCommand(options: RunCricketsCommandOptions): Promise { + const { + baseDirectory = __dirname, + spawnImpl = spawn, + ensureReadable = ensureReadableFile, + showHud, + showFailure, + } = options; + + const soundFilePath = resolveSoundFilePath(baseDirectory); + + try { + await ensureReadable(soundFilePath); + await playSoundFile(soundFilePath, spawnImpl); + await showHud(SUCCESS_HUD_MESSAGE); + } catch (error) { + await showFailure("Failed to play sound", toErrorMessage(error)); + } +} + +function toErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} diff --git a/test/play-crickets.test.ts b/test/play-crickets.test.ts new file mode 100644 index 0000000..04aa5a9 --- /dev/null +++ b/test/play-crickets.test.ts @@ -0,0 +1,76 @@ +import { EventEmitter } from "node:events"; +import { access } from "node:fs/promises"; +import test from "node:test"; +import assert from "node:assert/strict"; +import path from "node:path"; + +import { + SUCCESS_HUD_MESSAGE, + resolveSoundFilePath, + runCricketsCommand, + type RunCricketsCommandOptions, +} from "../src/lib/play-crickets"; + +test("resolveSoundFilePath resolves to bundled mp3", () => { + const resolved = resolveSoundFilePath("/tmp/example/src"); + assert.equal(resolved, path.resolve("/tmp/example/src", "../assets/crickets.mp3")); +}); + +test("runCricketsCommand shows HUD when playback starts", async () => { + const hudMessages: string[] = []; + const failures: Array<{ title: string; message: string }> = []; + + class FakeChild extends EventEmitter {} + + const options: RunCricketsCommandOptions = { + ensureReadable: async () => {}, + spawnImpl: () => { + const child = new FakeChild() as unknown as ReturnType>; + process.nextTick(() => { + (child as unknown as EventEmitter).emit("spawn"); + }); + return child; + }, + showHud: async (message) => { + hudMessages.push(message); + }, + showFailure: async (title, message) => { + failures.push({ title, message }); + }, + }; + + await runCricketsCommand(options); + + assert.deepEqual(hudMessages, [SUCCESS_HUD_MESSAGE]); + assert.equal(failures.length, 0); +}); + +test("runCricketsCommand shows failure when asset is missing", async () => { + const hudMessages: string[] = []; + const failures: Array<{ title: string; message: string }> = []; + + await runCricketsCommand({ + ensureReadable: async () => { + throw new Error("ENOENT: missing file"); + }, + spawnImpl: () => { + throw new Error("spawn should not run"); + }, + showHud: async (message) => { + hudMessages.push(message); + }, + showFailure: async (title, message) => { + failures.push({ title, message }); + }, + }); + + assert.equal(hudMessages.length, 0); + assert.equal(failures.length, 1); + assert.equal(failures[0].title, "Failed to play sound"); + assert.match(failures[0].message, /missing file/); +}); + +test("assets/crickets.mp3 exists in repository", async () => { + const soundAssetPath = path.join(process.cwd(), "assets/crickets.mp3"); + await access(soundAssetPath); +});