diff --git a/.gitignore b/.gitignore index b422ea4..c01f027 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,7 @@ functions/firebase-debug.log openloop-google-service-account-key-DO-NOT-COMMIT.json *service-account*.json *-DO-NOT-COMMIT.* + +# Firebase Functions local secrets (for emulator / functions:shell) — NEVER commit +*.secret.local +functions/.secret.local diff --git a/docs/FIREBASE.md b/docs/FIREBASE.md index bfa1678..366414e 100644 --- a/docs/FIREBASE.md +++ b/docs/FIREBASE.md @@ -64,11 +64,62 @@ The `GITHUB_TOKEN` secret the Cloud Function uses to file GitHub issues is a **f firebase functions:list # is the function deployed? firebase deploy --only functions:crashlytics-autotriage # (re)deploy firebase functions:secrets:set GITHUB_TOKEN # set/rotate the token (paste at hidden prompt) -firebase functions:secrets:access GITHUB_TOKEN # confirm a value exists (does not print it) +firebase functions:secrets:access GITHUB_TOKEN # reveal the stored value (handy for .secret.local) ``` To read function logs (e.g. to see a `401` from an expired token): Firebase Console → Functions → `crashlyticsToGithub` → Logs, or Google Cloud Console → Logging. +## Testing locally — fake a crash end-to-end (`functions:shell`) + +You can exercise the whole chain (function → GitHub issue → workflow → Claude) without waiting +for a real crash, using the Firebase **functions shell**. It runs the real function code locally +but **really** calls GitHub, so it creates a real issue that really triggers the workflow. + +**One-time setup** + +1. The shell needs `firebase-admin` installed: + + ``` + cd functions + npm install --save firebase-admin + ``` + +2. Create `functions/.secret.local` (gitignored — never commit) holding the token the function + uses, so `GITHUB_TOKEN.value()` resolves locally: + + ``` + GITHUB_TOKEN= + ``` + + Retrieve the value if you don't have it saved: `firebase functions:secrets:access GITHUB_TOKEN` + +**Fire a fake crash** + +``` +cd functions +firebase functions:shell +``` + +At the `firebase >` prompt, invoke the function. The object you pass becomes the event's `data`, +so the issue must sit at `payload.issue` — **no outer `data:` wrapper** — and use **plain ASCII**: + +```js +crashlyticsToGithub({payload:{issue:{id:'',title:'Fake crash - e2e test',subtitle:'NullPointerException',appVersion:'1.2.3'}}}) +``` + +Expected log line: `Created GitHub issue #NN for Crashlytics `. Type `.exit` to leave the shell. + +**Gotchas (learned the hard way)** + +- **Payload shape:** pass `{payload:{issue:{...}}}`. Wrapping it as `{data:{payload:...}}` makes the + id read as `unknown` (the shell already nests your argument under `event.data`). +- **Plain ASCII only:** an em-dash (`—`) in a value throws `SyntaxError ... in JSON`. Use `-`. +- **`firebase-admin` required:** the emulator refuses to load functions without it. +- **ADC warning is expected:** the shell uses your local Application Default Credentials and hits + **production** GitHub — fine for testing, but it creates real issues. Close the test issues after. +- **Dedupe 422:** currently logs `Dedupe search failed (422); proceeding to create.` — non-fatal + (the issue still gets created) but duplicate-suppression isn't working via this path yet; revisit. + ## Troubleshooting — in priority order 1. **No new GitHub issues from crashes?** → **Token expired** (most likely). Renew it (top of this doc). Then check the function logs for `401`. diff --git a/functions/package.json b/functions/package.json index aa9b483..d2ed7ba 100644 --- a/functions/package.json +++ b/functions/package.json @@ -7,6 +7,7 @@ "node": "22" }, "dependencies": { + "firebase-admin": "^14.0.0", "firebase-functions": "^6.0.0" } }