fresh-squeezy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 fresh-squeezy contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # fresh-squeezy
2
+
3
+ Validator-first Lemon Squeezy setup doctor. Verify your integration before it ships.
4
+
5
+ Library + CLI. One call, one structured report, one exit code.
6
+
7
+ ---
8
+
9
+ ## Why this exists
10
+
11
+ The official [`@lemonsqueezy/lemonsqueezy.js`](https://github.com/lmsqueezy/lemonsqueezy.js) SDK is great at making API calls. But integration bugs almost never live in the API calls — they live in configuration. Wrong store. Wrong mode. Unpublished product. Webhook registered but missing the one event your refund flow depends on. Live API key accidentally loaded in staging.
12
+
13
+ `fresh-squeezy` answers a different question fast:
14
+
15
+ > Is my API key pointed at the right store, in the right mode, with a product that is actually published and a webhook subscribed to the events my app relies on?
16
+
17
+ ### The pain it removes
18
+
19
+ - **Postman + dashboard ping-pong.** Today you copy IDs out of the Lemon Squeezy UI, paste them into env files, and hit Postman to verify each one. One CLI call replaces that loop.
20
+ - **SDK lag.** The official JS SDK last shipped v4.0.0 on 2024-11-05. The platform has added three behaviors since (`affiliate_activated`, `payment_processor`, `customer_updated`) that the SDK does not surface. `fresh-squeezy` tracks them in `src/support/manifest.ts` and a weekly drift workflow files an issue the moment the changelog moves again.
21
+ - **The scariest bug: prod-in-staging.** `fresh-squeezy` calls `/v1/users/me` and compares the `meta.test_mode` flag (API changelog 2024-01-05) against the mode you declared. Mismatch = `MODE_MISMATCH` error, doctor exits 1. Neither the SDK nor a hand-rolled wrapper catches this by default.
22
+ - **Repeat work across products.** Every new SaaS inside a company repeats the same billing setup dance. This is one place for the checks so the next product integration is a five-minute job, not a week of yak-shaving.
23
+
24
+ ### Why a validator, not another SDK
25
+
26
+ Most teams already use the official SDK or plain `fetch` for API calls. They don't need another wrapper — they need **fewer silent misconfigurations**. `fresh-squeezy` is intentionally thin:
27
+
28
+ - 4 validators (`connection`, `store`, `product`, `webhook`) that return the same `ValidationResult` shape every time
29
+ - `doctor()` composes them into one report
30
+ - A raw `request()` escape hatch so you never hit a wall when the platform adds something we haven't wrapped yet
31
+ - Static, reviewed support manifest — no live scraping in runtime code
32
+ - Stable issue codes so CI can branch on findings
33
+
34
+ If a check feels magical, something is wrong.
35
+
36
+ ### Open source by design
37
+
38
+ This project is MIT-licensed and deliberately scoped to stay small. The goal is to make it easy for any team (ours or yours) to drop it into a new product, wire it into CI, and trust the output. Contributions that keep it boring — more coverage, more validators, better error messages — are exactly the shape we want. See [CONTRIBUTING.md](./CONTRIBUTING.md).
39
+
40
+ ---
41
+
42
+ ## What it checks
43
+
44
+ | Validator | Catches |
45
+ | ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
46
+ | `connection` | Invalid key, unreachable account, no stores, **declared mode ≠ key's actual mode** (`MODE_MISMATCH`) |
47
+ | `store` | Wrong store ID, store owned by a different account |
48
+ | `product` | Unpublished product, product on the wrong store, missing or all-draft variants, missing buy URL |
49
+ | `webhook` | Webhook URL not registered, missing recommended events (order lifecycle, subscription lifecycle, refunds), missing optional newer events |
50
+
51
+ Every validator returns the same `ValidationResult` — stable public contract, switchable by `issue.code`.
52
+
53
+ ---
54
+
55
+ ## Install
56
+
57
+ ```bash
58
+ npm install fresh-squeezy
59
+ # or
60
+ pnpm add fresh-squeezy
61
+ ```
62
+
63
+ Requires Node.js 20+.
64
+
65
+ ## Setup (90 seconds)
66
+
67
+ ```bash
68
+ cp .env.example .env.local
69
+ # fill in LEMON_SQUEEZY_API_KEY (test or live)
70
+ npx fresh-squeezy doctor --all-stores
71
+ ```
72
+
73
+ That's the entire onboarding. No store ID to copy from the dashboard — the CLI discovers reachable stores itself.
74
+
75
+ ## Quick start — CLI
76
+
77
+ ```bash
78
+ # TTY: multi-select stores interactively, run doctor on each
79
+ npx fresh-squeezy doctor
80
+
81
+ # Non-interactive: every reachable store
82
+ npx fresh-squeezy doctor --all-stores
83
+
84
+ # Specific stores
85
+ npx fresh-squeezy doctor --store-ids 12,34,56
86
+
87
+ # Scope the run to a product + webhook
88
+ npx fresh-squeezy doctor --store-ids 12 \
89
+ --product-id 987 \
90
+ --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
91
+
92
+ # Single validator
93
+ npx fresh-squeezy validate webhook \
94
+ --store-ids 12,34 \
95
+ --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
96
+
97
+ # Machine-readable output for CI
98
+ npx fresh-squeezy doctor --all-stores --json
99
+ ```
100
+
101
+ Exit codes:
102
+
103
+ | Code | Meaning |
104
+ | ---- | ---------------------------------------------- |
105
+ | `0` | All validators passed |
106
+ | `1` | One or more validators reported `error`-level |
107
+ | `2` | Fatal error (missing key, invalid flags, etc.) |
108
+
109
+ ### Store resolution
110
+
111
+ Resolution order used by every store-scoped command:
112
+
113
+ 1. `--store-ids 1,2,3` (comma-separated, explicit)
114
+ 2. `--all-stores` (every store reachable with the key)
115
+ 3. TTY: inquirer multi-select
116
+ 4. No TTY + no flag: run connection-only (useful as a CI smoke check)
117
+
118
+ ## Quick start — library
119
+
120
+ ```ts
121
+ import { createFreshSqueezy } from "fresh-squeezy";
122
+
123
+ const lemon = createFreshSqueezy(); // reads LEMON_SQUEEZY_API_KEY, LEMON_SQUEEZY_MODE
124
+
125
+ const report = await lemon.doctor({
126
+ storeId: 12, // library is single-store per call
127
+ productId: 987,
128
+ webhookUrl: "https://app.example.com/api/webhooks/lemon-squeezy",
129
+ });
130
+
131
+ if (!report.ok) {
132
+ for (const result of report.results) {
133
+ for (const issue of result.issues) {
134
+ console.error(`[${issue.severity}] ${issue.code}: ${issue.message}`);
135
+ }
136
+ }
137
+ process.exit(1);
138
+ }
139
+ ```
140
+
141
+ For multi-store runs at the library layer, call `doctor()` in a loop across the store IDs you care about — the CLI does exactly this.
142
+
143
+ ## Sandbox vs live
144
+
145
+ Lemon Squeezy serves both modes from the same API host. Mode is determined by which key you use. `fresh-squeezy` surfaces the mode on every result **and** cross-checks the declared mode against the key's actual mode using `meta.test_mode` from `/v1/users/me` (API changelog 2024-01-05). If they disagree, `validateConnection` fires `MODE_MISMATCH` as an error and `doctor` exits 1 — that's the fastest way to catch a prod key pointed at staging (or vice versa) before it does damage.
146
+
147
+ ```ts
148
+ const lemon = createFreshSqueezy({ mode: "test" });
149
+ const result = await lemon.validateConnection();
150
+ console.log(result.mode); // "test" (declared)
151
+ console.log(result.resource?.actualMode); // "live" — alarm bell
152
+ ```
153
+
154
+ The CLI default is `--mode test`. Override with `--mode live`.
155
+
156
+ Live smoke testing in CI: the repo ships an opt-in `npm run test:live` target gated on `LEMON_SQUEEZY_LIVE_SMOKE=1`. Run it nightly with a secret test-mode key so platform drift surfaces before a release.
157
+
158
+ ## API
159
+
160
+ ### `createFreshSqueezy(config?)`
161
+
162
+ ```ts
163
+ createFreshSqueezy({
164
+ apiKey?: string; // default: process.env.LEMON_SQUEEZY_API_KEY
165
+ storeId?: string | number; // optional — also read from env for lib consumers
166
+ mode?: "test" | "live"; // default: process.env.LEMON_SQUEEZY_MODE ?? "test"
167
+ baseUrl?: string; // default: "https://api.lemonsqueezy.com"
168
+ fetch?: typeof fetch; // default: globalThis.fetch
169
+ });
170
+ ```
171
+
172
+ Returns a `FreshSqueezyClient`:
173
+
174
+ ```ts
175
+ client.mode
176
+ client.request(options) // raw escape hatch
177
+ client.validateConnection()
178
+ client.validateStore(id)
179
+ client.validateProduct({ productId, expectedStoreId? })
180
+ client.validateWebhook({ storeId, url })
181
+ client.doctor({ storeId?, productId?, webhookUrl? })
182
+ ```
183
+
184
+ ### `ValidationResult<T>`
185
+
186
+ ```ts
187
+ {
188
+ ok: boolean;
189
+ mode: "test" | "live";
190
+ name: string;
191
+ resource?: T;
192
+ issues: Array<{
193
+ code: string;
194
+ severity: "info" | "warning" | "error";
195
+ message: string;
196
+ suggestedFix?: string;
197
+ context?: Record<string, string | number | boolean | null>;
198
+ }>;
199
+ }
200
+ ```
201
+
202
+ Switch on `issue.code` in CI logic — codes are stable across minor versions.
203
+
204
+ ### Raw escape hatch
205
+
206
+ For endpoints not yet wrapped (new changelog entries, License API, affiliates):
207
+
208
+ ```ts
209
+ const user = await lemon.request({ path: "/v1/users/me" });
210
+ ```
211
+
212
+ ## Environment variables
213
+
214
+ Only two matter to the CLI:
215
+
216
+ | Variable | Required | Used by | Purpose |
217
+ | ------------------------ | -------- | ----------------- | ------------------------------------------ |
218
+ | `LEMON_SQUEEZY_API_KEY` | yes | library + CLI | Bearer token |
219
+ | `LEMON_SQUEEZY_MODE` | no | library + CLI | `test` (default) or `live` |
220
+ | `LEMON_SQUEEZY_STORE_ID` | no | library consumers | Convenience default for `client.doctor()` |
221
+
222
+ The CLI does **not** read `LEMON_SQUEEZY_STORE_ID` — use `--store-ids` or `--all-stores` so store selection stays explicit per-command.
223
+
224
+ ## Changelog drift watcher
225
+
226
+ A weekly GitHub Action fetches the [Lemon Squeezy API changelog](https://docs.lemonsqueezy.com/api/getting-started/changelog), hashes the normalized content, and compares it against `src/support/changelog-snapshot.json`. On drift, it opens a labeled issue with previous/current hashes, a link to the workflow run, and refresh instructions. Advisory only — no runtime code ever scrapes the changelog.
227
+
228
+ Tracked platform additions beyond the official SDK (as of 2026-04-24):
229
+
230
+ - `customer_updated` webhook event — added **2026-02-25**
231
+ - `payment_processor` property on Subscription — added **2025-06-11**
232
+ - Affiliates endpoints + `affiliate_activated` webhook — added **2025-01-21**
233
+ - `test_mode` flag on `/v1/users/me` — added **2024-01-05** (powers `MODE_MISMATCH`)
234
+
235
+ ## Scope (v1)
236
+
237
+ **In**: connection, store, product, webhook validators; `doctor()`; library + CLI; sandbox-mode fixture tests + opt-in live smoke.
238
+
239
+ **Out**: License API, affiliates, changelog scraper in runtime, dashboard UI. On the roadmap for v2 if demand is real.
240
+
241
+ ## Contributing
242
+
243
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). TL;DR: clone, `npm install`, `npm test`. Manual QA steps in [docs/MANUAL_QA.md](./docs/MANUAL_QA.md).
244
+
245
+ ## License
246
+
247
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli.js";