|
1 | 1 | --- |
2 | 2 | name: pipedrive-review-marketplace-readiness |
3 | | -description: Review a Pipedrive Marketplace integration project for readiness. Checks the project against the marketplace checklist and reports what passes, fails, or needs manual review. |
| 3 | +description: Review a Pipedrive Marketplace integration project for readiness. Checks the project for missing requirements and reports only what needs to be fixed before submission. |
4 | 4 | allowed-tools: [Read, Bash] |
5 | 5 | --- |
6 | 6 |
|
7 | 7 | # Marketplace Readiness Review |
8 | 8 |
|
| 9 | +Checks 4 requirements for marketplace submission readiness. Reports only what is missing. If nothing is missing, the app is ready to submit. |
| 10 | + |
9 | 11 | ## Precondition |
10 | 12 |
|
11 | | -Read `marketplace-checklist.md` from the current directory. If it does not exist, tell the developer that this file is generated by `npx create-pipedrive-app` — they need to scaffold their project first with that command. |
| 13 | +Check that `src/app.ts` and `package.json` exist in the current directory. If they do not exist, tell the developer to run this skill from their project root and stop. |
12 | 14 |
|
13 | 15 | ## Gather project context |
14 | 16 |
|
15 | | -Read the following files to understand the current state of the project: |
| 17 | +Read these files: |
| 18 | + |
| 19 | +- `src/app.ts` |
| 20 | +- `package.json` |
| 21 | +- `src/pipedrive/client.ts` (if it exists) |
| 22 | +- `src/oauth/index.ts` (if it exists) |
| 23 | + |
| 24 | +Find all TypeScript source files: |
| 25 | + |
| 26 | +```bash |
| 27 | +find src -name "*.ts" | sort |
| 28 | +``` |
| 29 | + |
| 30 | +Read all files found. |
| 31 | + |
| 32 | +## Run all 4 checks |
| 33 | + |
| 34 | +Collect all failures before reporting. Do not stop at the first failure. |
| 35 | + |
| 36 | +--- |
| 37 | + |
| 38 | +### Check 1: Token refresh for non-SDK API calls |
| 39 | + |
| 40 | +Scan source files for `fetch(` or `axios` calls (including `axios.get`, `axios.post`, etc.) that include an Authorization header with a token value. |
| 41 | + |
| 42 | +For each match: |
| 43 | + |
| 44 | +1. Is it inside `src/pipedrive/client.ts`? → Skip. The SDK wrapper handles token refresh automatically via `onTokenUpdate`. |
| 45 | +2. Is the token taken directly from an OAuth response in the same function scope (e.g., `token.access_token` where `token` is the return value of `oauth2.authorize()`)? → Skip. This is a fresh token — no refresh needed. |
| 46 | +3. Is the token retrieved from the database (e.g., via `getTokenByCompany`, `stored.token`, `row.accessToken`, or similar)? → This is a stored token that can expire. Check if the same code path handles 401 responses by refreshing the token and retrying. |
| 47 | + |
| 48 | +**Fail condition:** A stored token is used in a direct HTTP call without 401/refresh handling. |
| 49 | + |
| 50 | +**Report if failing:** |
| 51 | + |
| 52 | +Identify the specific file and line. Then output: |
| 53 | + |
| 54 | + ❌ Token refresh not handled for direct API calls |
| 55 | + |
| 56 | + `src/path/to/file.ts` makes a direct HTTP call using a stored access token without handling 401 responses. When the token expires, this call will fail silently. |
| 57 | + |
| 58 | + Fix: Either use `getClient(companyId)` from `src/pipedrive/client.ts` (which handles refresh automatically), or add a 401 retry that refreshes the token and retries the request. |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +### Check 2: HTTPS enforced |
| 63 | + |
| 64 | +**Sub-check A — no hardcoded HTTP URLs:** |
| 65 | + |
| 66 | +Run: |
| 67 | + |
| 68 | +```bash |
| 69 | +grep -rn "http://" src --include="*.ts" | grep -v "localhost" | grep -v "127.0.0.1" |
| 70 | +``` |
| 71 | + |
| 72 | +Any output is a failure. List each match with file and line number. |
| 73 | + |
| 74 | +**Report if sub-check A failing:** |
| 75 | + |
| 76 | + ❌ Hardcoded HTTP URLs found |
| 77 | + |
| 78 | + The following URLs use HTTP instead of HTTPS: |
| 79 | + - `src/path/to/file.ts:42`: `http://example.com/endpoint` |
| 80 | + |
| 81 | + Fix: Replace with `https://` URLs. Pipedrive requires all external calls to use HTTPS in production. |
| 82 | + |
| 83 | +**Sub-check B — trust proxy configured:** |
| 84 | + |
| 85 | +Run: |
| 86 | + |
| 87 | +```bash |
| 88 | +grep -n "trust proxy" src/app.ts |
| 89 | +``` |
| 90 | + |
| 91 | +No output is a failure. |
| 92 | + |
| 93 | +**Report if sub-check B failing:** |
| 94 | + |
| 95 | + ❌ trust proxy not configured |
16 | 96 |
|
17 | | -- `src/app.ts` — confirms OAuth and extension routers are mounted |
18 | | -- `src/oauth/index.ts` — confirms OAuth flow is implemented |
19 | | -- `.env.example` — confirms required env vars are documented |
20 | | -- `docker-compose.yml` — confirms containerised dev setup |
21 | | -- `package.json` — confirms project name, dependencies, and scripts |
| 97 | + `src/app.ts` does not set `trust proxy`. Without it, Express cannot detect the protocol used by the |
| 98 | + client when the app runs behind a reverse proxy (Nginx, load balancer, etc.). This breaks HTTPS |
| 99 | + enforcement and secure cookie handling in production. |
22 | 100 |
|
23 | | -## Review |
| 101 | + Fix: Add to `src/app.ts` before any route definitions: |
24 | 102 |
|
25 | | -For each item in `marketplace-checklist.md`, determine whether the project: |
26 | | -- ✅ **Passes** — evidence found in the code |
27 | | -- ❌ **Fails** — required code or config is missing or incorrect |
28 | | -- ⚠️ **Unclear** — cannot be determined from static analysis alone (requires manual testing or runtime verification) |
| 103 | + ```ts |
| 104 | + app.set('trust proxy', 1); |
| 105 | + ``` |
29 | 106 |
|
30 | | -## Report |
| 107 | +--- |
| 108 | + |
| 109 | +### Check 3: Error handling |
| 110 | + |
| 111 | +**Sub-check A — Express error handler in app.ts:** |
| 112 | + |
| 113 | +Run: |
| 114 | + |
| 115 | +```bash |
| 116 | +grep -n "NextFunction" src/app.ts |
| 117 | +``` |
| 118 | + |
| 119 | +Look for a 4-argument middleware with the signature `(err: Error, _req: Request, res: Response, _next: NextFunction)`. If no such handler exists, this sub-check fails. |
| 120 | + |
| 121 | +**Report if sub-check A failing:** |
| 122 | + |
| 123 | + ❌ No Express error handler |
| 124 | + |
| 125 | + `src/app.ts` has no error-handling middleware. Unhandled errors will crash the process or send empty |
| 126 | + responses to the client. |
| 127 | + |
| 128 | + Fix: Add before `export default app`: |
| 129 | + |
| 130 | + ```ts |
| 131 | + app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => { |
| 132 | + console.error(err); |
| 133 | + res.status(500).json({ error: err.message }); |
| 134 | + }); |
| 135 | + ``` |
| 136 | + |
| 137 | +**Sub-check B — async route handlers with try/catch:** |
| 138 | + |
| 139 | +Run: |
| 140 | + |
| 141 | +```bash |
| 142 | +grep -rn "async.*req.*res" src --include="*.ts" |
| 143 | +``` |
| 144 | + |
| 145 | +For each async route handler found, read the surrounding code and verify the function body has a try/catch block that calls `next(err)` in the catch. If any handler lacks it, this sub-check fails. |
| 146 | + |
| 147 | +**Report if sub-check B failing:** |
| 148 | + |
| 149 | + ❌ Async route handlers without error handling |
| 150 | + |
| 151 | + The following route handlers do not catch errors: |
| 152 | + - `src/path/to/file.ts:15` |
| 153 | + |
| 154 | + Fix: Wrap each async handler body in try/catch and pass errors to next: |
| 155 | + |
| 156 | + ```ts |
| 157 | + router.get('/example', async (req, res, next) => { |
| 158 | + try { |
| 159 | + // handler logic |
| 160 | + } catch (err) { |
| 161 | + next(err); |
| 162 | + } |
| 163 | + }); |
| 164 | + ``` |
| 165 | + |
| 166 | +--- |
| 167 | + |
| 168 | +### Check 4: Rate limit handling |
| 169 | + |
| 170 | +Run: |
| 171 | + |
| 172 | +```bash |
| 173 | +grep -rn "429\|X-RateLimit\|Retry-After" src --include="*.ts" |
| 174 | +``` |
| 175 | + |
| 176 | +**Fail condition:** No matches found. |
| 177 | + |
| 178 | +**Report if failing:** |
| 179 | + |
| 180 | + ❌ No rate limit handling |
| 181 | + |
| 182 | + Pipedrive enforces API rate limits per OAuth token. When the limit is exceeded, the API returns HTTP |
| 183 | + 429. Without handling this, your app will silently fail under load. |
| 184 | + |
| 185 | + Headers returned on every API response: |
| 186 | + - `X-RateLimit-Limit` — requests allowed per 10 seconds |
| 187 | + - `X-RateLimit-Remaining` — requests remaining in the current window |
| 188 | + - `X-RateLimit-Reset` — Unix timestamp when the limit resets |
| 189 | + |
| 190 | + Fix: Add 429 handling to your API calls. Example retry pattern: |
| 191 | + |
| 192 | + ```ts |
| 193 | + async function callWithRetry(fn: () => Promise<Response>): Promise<Response> { |
| 194 | + const response = await fn(); |
| 195 | + if (response.status === 429) { |
| 196 | + const resetAt = Number(response.headers.get('X-RateLimit-Reset')) * 1000; |
| 197 | + const delay = Math.max(resetAt - Date.now(), 1000); |
| 198 | + await new Promise(resolve => setTimeout(resolve, delay)); |
| 199 | + return fn(); |
| 200 | + } |
| 201 | + return response; |
| 202 | + } |
| 203 | + ``` |
| 204 | + |
| 205 | + Reference: https://pipedrive.readme.io/docs/core-api-concepts-rate-limiting#http-headers-and-response-codes |
| 206 | + |
| 207 | +--- |
31 | 208 |
|
32 | | -Present results in three sections: |
| 209 | +## Output |
33 | 210 |
|
34 | | -**Passing (✅)** |
35 | | -List each passing item with a one-line explanation of what you found. |
| 211 | +If all 4 checks pass: |
36 | 212 |
|
37 | | -**Action required (❌)** |
38 | | -For each failing item, provide: |
39 | | -- What is missing or wrong |
40 | | -- The specific change needed to fix it |
| 213 | + ✅ All marketplace readiness checks passed. Your app is ready to submit. |
41 | 214 |
|
42 | | -**Manual review needed (⚠️)** |
43 | | -List items that pass static checks but require the developer to verify at runtime (e.g. OAuth redirect actually works, extension iframe loads in Pipedrive). |
| 215 | +If any checks fail, output only the failing checks using the report formats above. Do not mention passing checks. |
0 commit comments