fresh-squeezy 0.1.8 → 0.1.10

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/README.md CHANGED
@@ -1,63 +1,116 @@
1
- # fresh-squeezy
2
-
3
- Validator-first Lemon Squeezy doctor. Catches misconfigurations before they ship. CLI + library, Node 20+.
1
+ <!-- Canonical README. Translations (README.zh-CN.md, README.ja.md, README.es.md, README.pt-BR.md) are derived from this file; English is the source of truth. -->
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/YosefHayim/fresh-squeezy">
5
+ <img src="assets/fresh-squeezy-hero.png" alt="fresh-squeezy — the validator-first doctor for your Lemon Squeezy setup. Catch billing and webhook misconfigurations before they ship." width="640" />
6
+ </a>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <strong>The doctor for your Lemon Squeezy setup — catch billing &amp; webhook misconfigurations before they ship.</strong>
11
+ </p>
12
+
13
+ <!-- Badges. tests count is static; bump it on major test-suite changes. -->
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/fresh-squeezy"><img src="https://img.shields.io/npm/v/fresh-squeezy?logo=npm&amp;color=cb3837" alt="npm version" /></a>
16
+ <a href="https://www.npmjs.com/package/fresh-squeezy"><img src="https://img.shields.io/npm/dm/fresh-squeezy?logo=npm&amp;color=cb3837" alt="npm downloads per month" /></a>
17
+ <a href="https://github.com/YosefHayim/fresh-squeezy/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/YosefHayim/fresh-squeezy/ci.yml?branch=main&amp;logo=github&amp;label=CI" alt="CI status" /></a>
18
+ <a href="./LICENSE"><img src="https://img.shields.io/npm/l/fresh-squeezy?color=3fb950" alt="MIT license" /></a>
19
+ <img src="https://img.shields.io/node/v/fresh-squeezy?logo=node.js&amp;logoColor=white&amp;color=339933" alt="Node.js 20 or newer" />
20
+ <img src="https://img.shields.io/npm/types/fresh-squeezy?logo=typescript&amp;logoColor=white" alt="TypeScript types included" />
21
+ <a href="https://packagephobia.com/result?p=fresh-squeezy"><img src="https://packagephobia.com/badge?p=fresh-squeezy" alt="install size" /></a>
22
+ <img src="https://img.shields.io/badge/tests-154%20passing-3fb950?logo=vitest&amp;logoColor=white" alt="154 tests passing" />
23
+ </p>
24
+
25
+ <p align="center">
26
+ <b>English</b> ·
27
+ <a href="./README.zh-CN.md">简体中文</a> ·
28
+ <a href="./README.ja.md">日本語</a> ·
29
+ <a href="./README.es.md">Español</a> ·
30
+ <a href="./README.pt-BR.md">Português</a>
31
+ </p>
32
+
33
+ <p align="center">
34
+ <a href="#30-second-start">Quick start</a> ·
35
+ <a href="#what-it-catches-that-postman-and-the-official-sdk-wont">What it catches</a> ·
36
+ <a href="#fresh-squeezy-vs-the-alternatives">Comparison</a> ·
37
+ <a href="#cli">CLI</a> ·
38
+ <a href="#library">Library</a> ·
39
+ <a href="#issue-codes">Issue codes</a> ·
40
+ <a href="#faq">FAQ</a>
41
+ </p>
42
+
43
+ ---
44
+
45
+ **fresh-squeezy** is a CLI and TypeScript library that validates your [Lemon Squeezy](https://www.lemonsqueezy.com/) billing integration — stores, products, webhooks, discounts, license keys, and subscription plans — and catches misconfigurations before they ship. Run it as a one-command `doctor` locally or in CI: it returns stable [exit codes](#30-second-start) and machine-readable JSON, and it tracks [Lemon Squeezy API changelog](https://docs.lemonsqueezy.com/api/getting-started/changelog) drift the official SDK hasn't shipped yet. Node 20+.
4
46
 
5
47
  ## 30-second start
6
48
 
7
49
  ```bash
8
- npm i -D fresh-squeezy
9
- cp .env.example .env.local # fill in LEMON_SQUEEZY_API_KEY
10
- npx fresh-squeezy doctor --all-stores
50
+ npx fresh-squeezy
11
51
  ```
12
52
 
13
- No store ID to copy from the dashboard — the CLI discovers reachable stores itself.
53
+ The first run adds `fresh-squeezy` to devDependencies when it is missing, then starts the guided setup. No store ID to copy from the dashboard — the CLI discovers reachable stores itself. Use `npx fresh-squeezy --no-install` to run the setup without editing `package.json`.
14
54
 
15
55
  | Exit | Meaning |
16
56
  |------|---------|
17
57
  | `0` | All validators passed |
18
58
  | `1` | One or more validators reported `error`-level issues |
19
59
  | `2` | Fatal (missing key, invalid flags, network failure) |
60
+ | `130` | User cancelled an interactive flow |
20
61
 
21
62
  ## What it catches that Postman and the official SDK won't
22
63
 
23
64
  - **Prod key pointed at staging.** `MODE_MISMATCH` fires when the key's true `meta.test_mode` (API changelog 2024-01-05) disagrees with the declared mode. Doctor exits 1. Neither the SDK nor a hand-rolled wrapper catches this by default.
24
65
  - **Silent store-ownership mismatches.** Products, discounts, license keys, and subscription plans whose `store_id` doesn't match the store you scoped the run to. Stable codes: `PRODUCT_WRONG_STORE`, `DISCOUNT_STORE_MISMATCH`, `LICENSE_KEY_STORE_MISMATCH`, `PLAN_STORE_MISMATCH`.
25
66
  - **Webhook subscribed to the wrong events.** Diff against a manifest of recommended events (order/subscription lifecycle, refunds) and newer-but-optional events the SDK doesn't ship.
26
- - **Platform drift.** A weekly GitHub Action hashes the [Lemon Squeezy API changelog](https://docs.lemonsqueezy.com/api/getting-started/changelog) against `src/support/changelog-snapshot.json` and opens an issue when it moves. Tracked additions beyond the official SDK as of 2026-04-24: `customer_updated` (2026-02-25), `payment_processor` on Subscription (2025-06-11), Affiliates + `affiliate_activated` (2025-01-21), `test_mode` on `/v1/users/me` (2024-01-05).
67
+ - **Platform drift.** A weekly GitHub Action hashes the [Lemon Squeezy API changelog](https://docs.lemonsqueezy.com/api/getting-started/changelog) against `src/support/changelog-snapshot.json`, refreshes docs-derived API types, and opens follow-up work when policy decisions are needed. Tracked additions include `customer_updated` (2026-02-25), `payment_processor` on Subscription (2025-06-11), Affiliates + `affiliate_activated` (2025-01-21), order item `quantity` (2024-12-06), checkout styling / `skip_trial` / `variant_quantities`, subscription invoice refund fields, and `test_mode` on `/v1/users/me` (2024-01-05).
27
68
  - **Postman + dashboard ping-pong.** One `doctor` call replaces the loop of copying IDs out of the UI, pasting them into env files, and verifying each one by hand.
28
69
 
70
+ ## fresh-squeezy vs the alternatives
71
+
72
+ How a typical Lemon Squeezy pre-ship check compares across the tools a developer would otherwise reach for:
73
+
74
+ | Capability | fresh-squeezy | Official SDK | Postman | Hand-rolled wrapper |
75
+ |---|:---:|:---:|:---:|:---:|
76
+ | Mode / key mismatch detection (`MODE_MISMATCH`) | ✅ | ❌ | ❌ | ❌ |
77
+ | Store-ownership cross-checks | ✅ | ❌ | ❌ | ⚠️ manual |
78
+ | Webhook event-coverage diff | ✅ | ❌ | ⚠️ manual | ⚠️ manual |
79
+ | Discount / license-key / plan validation | ✅ | ❌ | ❌ | ⚠️ manual |
80
+ | Changelog-drift tracking | ✅ | ❌ | ❌ | ❌ |
81
+ | Stable, CI-ready exit codes + JSON | ✅ | ❌ | ❌ | ⚠️ manual |
82
+ | One-command full sweep (`doctor`) | ✅ | ❌ | ❌ | ❌ |
83
+ | Typed API responses | ✅ | ✅ | ❌ | ⚠️ depends |
84
+
85
+ fresh-squeezy is **not** a replacement for the official SDK — it's the pre-flight check you run *alongside* it. Use the SDK to make API calls; use fresh-squeezy to prove your setup is correct before those calls hit production.
86
+
29
87
  ## CLI
30
88
 
31
89
  ```bash
90
+ # First run: install as a dev dependency, then start guided setup
91
+ npx fresh-squeezy
92
+
93
+ # Guided setup only: reuse env values, pick a store, choose resource checks
94
+ npx fresh-squeezy init
95
+
32
96
  # TTY: multi-select stores interactively, run doctor on each
33
97
  npx fresh-squeezy doctor
34
98
 
35
- # Non-interactive: every reachable store
36
- npx fresh-squeezy doctor --all-stores
99
+ # Full sweep across every reachable store and resource
100
+ npx fresh-squeezy doctor --all-stores --all-resources
37
101
 
38
- # Specific stores
39
- npx fresh-squeezy doctor --store-ids 12,34,56
102
+ # Machine-readable full sweep for CI
103
+ npx fresh-squeezy doctor --all-stores --all-resources --json
40
104
 
41
- # Scope to a product + webhook
42
- npx fresh-squeezy doctor --store-ids 12 \
43
- --product-id 987 \
44
- --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
45
-
46
- # Single validator
105
+ # Single validator, scoped to specific stores
47
106
  npx fresh-squeezy validate webhook \
48
107
  --store-ids 12,34 \
49
108
  --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
50
-
51
- # Machine-readable output for CI
52
- npx fresh-squeezy doctor --all-stores --json
53
109
  ```
54
110
 
55
- Store resolution order, used by every store-scoped command:
111
+ Stores resolve in this order for every store-scoped command: explicit `--store-ids`, then `--all-stores`, then an interactive multi-select on a TTY, then a connection-only run when there's no TTY and no flag (useful as a CI smoke check). `doctor` validates connection and store access plus any explicit resource flags; add `--all-resources` to discover and validate every supported resource in the selected store(s).
56
112
 
57
- 1. `--store-ids 1,2,3` (comma-separated, explicit)
58
- 2. `--all-stores` (every store reachable with the key)
59
- 3. TTY: inquirer multi-select prompt
60
- 4. No TTY + no flag: connection-only run (useful as a CI smoke check)
113
+ **→ Full command, flag, and store-resolution reference: [docs/cli-reference.md](./docs/cli-reference.md)**
61
114
 
62
115
  ## Library
63
116
 
@@ -82,9 +135,15 @@ if (!report.ok) {
82
135
  }
83
136
  ```
84
137
 
85
- For multi-store runs at the library layer, call `doctor()` in a loop. The CLI does exactly this.
138
+ For multi-store runs at the library layer, call `doctor()` in a loop — the CLI does exactly this. Switch on `issue.code` in CI logic; codes are stable across minor versions.
139
+
140
+ Public types: [`FreshSqueezyClient`](src/createFreshSqueezy.ts), [`ValidationResult<T>`](src/core/types.ts), [`DoctorReport`](src/core/types.ts), resource attribute interfaces under [`src/resources`](src/resources), docs-generated Lemon Squeezy object types in [`src/generated/lemonSqueezyApiTypes.ts`](src/generated/lemonSqueezyApiTypes.ts), and changelog augmentation helpers in [`src/augmentations.ts`](src/augmentations.ts).
141
+
142
+ For endpoints not yet wrapped, use the raw escape hatch:
86
143
 
87
- Public types: [`FreshSqueezyClient`](src/createFreshSqueezy.ts), [`ValidationResult<T>`](src/core/types.ts), [`DoctorReport`](src/core/types.ts). Switch on `issue.code` in CI logic — codes are stable across minor versions.
144
+ ```ts
145
+ const user = await lemon.request({ path: "/v1/users/me" });
146
+ ```
88
147
 
89
148
  ## Sandbox vs live
90
149
 
@@ -97,63 +156,91 @@ result.mode; // "test" (declared)
97
156
  result.resource?.actualMode; // "live" — alarm bell
98
157
  ```
99
158
 
100
- The CLI default is `--mode test`. Override with `--mode live`. For nightly platform-drift checks in CI, run `npm run test:live` with `LEMON_SQUEEZY_LIVE_SMOKE=1` and a test-mode key.
159
+ The CLI default is `--mode test`. Override with `--mode live`. Guided setup asks for explicit confirmation before continuing with a detected live-mode key. For nightly platform-drift checks in CI, run `npm run test:live` with `LEMON_SQUEEZY_LIVE_SMOKE=1` and a test-mode key.
160
+
161
+ ## Environment variables
162
+
163
+ | Variable | Required | Purpose |
164
+ |----------|----------|---------|
165
+ | `LEMON_SQUEEZY_API_KEY` | yes | Bearer token (library + CLI) |
166
+ | `LEMON_SQUEEZY_MODE` | no | `test` (default) or `live` |
167
+ | `LEMON_SQUEEZY_STORE_ID` | no | Convenience default for `client.doctor()` — library only |
168
+
169
+ The CLI does not read `LEMON_SQUEEZY_STORE_ID`; use `--store-ids` or `--all-stores` so store selection stays explicit per command.
101
170
 
102
171
  ## Issue codes
103
172
 
104
- Switch on `issue.code` in CI. All codes are stable across minor versions.
173
+ Switch on `issue.code` in CI — all codes are stable across minor versions. The most common ones:
105
174
 
106
175
  | Code | Meaning |
107
176
  |------|---------|
108
177
  | `AUTH_FAILED` | Invalid or missing API key |
109
178
  | `MODE_MISMATCH` | Declared mode doesn't match key's `meta.test_mode` |
110
- | `NETWORK_ERROR` | Lemon Squeezy unreachable |
111
179
  | `STORE_NOT_FOUND` / `STORE_NOT_OWNED` | Store ID invalid or owned by another account |
112
180
  | `PRODUCT_UNPUBLISHED` / `PRODUCT_WRONG_STORE` / `PRODUCT_NO_BUY_URL` | Product can't accept checkout |
113
- | `VARIANT_MISSING` / `VARIANT_UNPUBLISHED` | Product has no live variants |
114
- | `WEBHOOK_NOT_FOUND` / `WEBHOOK_EVENTS_MISSING` / `WEBHOOK_OPTIONAL_EVENTS` | Webhook URL not registered or under-subscribed |
115
- | `DISCOUNT_DRAFT` / `DISCOUNT_EXPIRED` / `DISCOUNT_NOT_STARTED` / `DISCOUNT_INVALID_AMOUNT` / `DISCOUNT_REDEMPTIONS_EXHAUSTED` / `DISCOUNT_STORE_MISMATCH` | Discount won't apply at checkout |
116
- | `LICENSE_KEY_DISABLED` / `LICENSE_KEY_EXPIRED` / `LICENSE_KEY_AT_ACTIVATION_LIMIT` / `LICENSE_KEY_STORE_MISMATCH` | License key won't activate |
117
- | `PLAN_NOT_SUBSCRIPTION` / `PLAN_INVALID_INTERVAL` / `PLAN_FREE_PRICE` / `PLAN_TRIAL_INCONSISTENT` / `PLAN_DRAFT` / `PLAN_STORE_MISMATCH` | Subscription plan misconfigured |
181
+ | `WEBHOOK_NOT_FOUND` / `WEBHOOK_EVENTS_MISSING` | Webhook URL not registered or under-subscribed |
118
182
 
119
- For endpoints not yet wrapped, use the raw escape hatch:
183
+ **→ Full issue-code reference (discounts, license keys, plans, variants, network) with escape-hatch examples: [docs/issue-codes.md](./docs/issue-codes.md)**
120
184
 
121
- ```ts
122
- const user = await lemon.request({ path: "/v1/users/me" });
185
+ ## Reference
186
+
187
+ Validators — each returns a stable `ValidationResult`:
188
+
189
+ - **`validateConnection`** — reachability, key validity, store presence, declared-vs-actual mode. [→ source](src/validate/connection.ts)
190
+ - **`validateStore`** — store ID exists and is owned by the key's account. [→ source](src/validate/store.ts)
191
+ - **`validateProduct`** — published, on the expected store, has live variants and a buy URL. [→ source](src/validate/product.ts)
192
+ - **`validateWebhook`** — webhook URL registered and subscribed to recommended events. [→ source](src/validate/webhook.ts)
193
+ - **`validateDiscount`** — active, in-window, valid amount, store ownership matches. [→ source](src/validate/discount.ts)
194
+ - **`validateLicenseKey`** — enabled, not expired, activations available, store ownership matches. [→ source](src/validate/licenseKey.ts)
195
+ - **`validateSubscriptionPlan`** — subscription type, valid interval, non-zero price, consistent trial. [→ source](src/validate/subscriptionPlan.ts)
196
+ - **`doctor`** — composes the above into one `DoctorReport`. [→ source](src/validate/doctor.ts)
197
+
198
+ Resource coverage is generated from Lemon Squeezy's object docs, so most newly documented fields don't need a hand edit:
199
+
200
+ ```bash
201
+ npm run generate:api-types
202
+ npm run check:api-types
123
203
  ```
124
204
 
125
- ## Environment variables
205
+ ## FAQ
126
206
 
127
- | Variable | Required | Purpose |
128
- |----------|----------|---------|
129
- | `LEMON_SQUEEZY_API_KEY` | yes | Bearer token (library + CLI) |
130
- | `LEMON_SQUEEZY_MODE` | no | `test` (default) or `live` |
131
- | `LEMON_SQUEEZY_STORE_ID` | no | Convenience default for `client.doctor()` — library only |
207
+ ### How do I check if my Lemon Squeezy webhook is subscribed to the right events?
132
208
 
133
- The CLI does not read `LEMON_SQUEEZY_STORE_ID`; use `--store-ids` or `--all-stores` so store selection stays explicit per command.
209
+ Run `npx fresh-squeezy validate webhook --store-ids <id> --webhook-url <url>`. fresh-squeezy diffs your webhook's subscribed events against a manifest of recommended order/subscription/refund events and reports `WEBHOOK_EVENTS_MISSING` for gaps, or `WEBHOOK_NOT_FOUND` if the URL isn't registered at all.
134
210
 
135
- ## Reference
211
+ ### How do I catch a Lemon Squeezy production key pointed at a test (staging) store?
212
+
213
+ That's the `MODE_MISMATCH` check. fresh-squeezy compares the mode you declared (`--mode` or `LEMON_SQUEEZY_MODE`) against the key's real `meta.test_mode` from `/v1/users/me`. When they disagree, `doctor` exits 1 — so a live key accidentally used in a staging deploy (or vice versa) fails the check before it reaches users.
214
+
215
+ ### Does fresh-squeezy work in CI?
216
+
217
+ Yes. Run `npx fresh-squeezy doctor --all-stores --all-resources --json` for a machine-readable full sweep. It returns stable [exit codes](#30-second-start) (`0` pass, `1` validation errors, `2` fatal) and stable `issue.code` strings you can assert on. No TTY required — without store flags it falls back to a connection-only smoke check.
136
218
 
137
- ### Validators
219
+ ### Is fresh-squeezy a replacement for the official Lemon Squeezy SDK?
138
220
 
139
- - **`validateConnection`** — Reachability, key validity, store presence, declared-vs-actual mode. [→ source](src/validate/connection.ts)
140
- - **`validateStore`** — Store ID exists and is owned by the key's account. [→ source](src/validate/store.ts)
141
- - **`validateProduct`** — Published, on the expected store, has live variants and a buy URL. [→ source](src/validate/product.ts)
142
- - **`validateWebhook`** — Webhook URL registered and subscribed to recommended events. [→ source](src/validate/webhook.ts)
143
- - **`validateDiscount`** — Active, in-window, valid amount, store ownership matches. [→ source](src/validate/discount.ts)
144
- - **`validateLicenseKey`** — Enabled, not expired, activations available, store ownership matches. [→ source](src/validate/licenseKey.ts)
145
- - **`validateSubscriptionPlan`** — Subscription type, valid interval, non-zero price, consistent trial. [→ source](src/validate/subscriptionPlan.ts)
146
- - **`doctor`** — Composes the above into one `DoctorReport`. [→ source](src/validate/doctor.ts)
221
+ No. The [official SDK](https://github.com/lmsqueezy/lemonsqueezy.js) makes API calls; fresh-squeezy is the pre-flight check that proves your setup is correct *before* those calls hit production. They're complementary — see the [comparison table](#fresh-squeezy-vs-the-alternatives).
147
222
 
148
- ### CLI commands
223
+ ### What is "changelog drift" and why should I care?
149
224
 
150
- - **`doctor`** — Run every configured validator and emit a report. [→ source](src/cli/commands/doctor.ts)
151
- - **`validate <name>`** — Run a single validator. [→ source](src/cli/commands/validate.ts)
152
- - **`init`** — Interactive setup: ask for credentials, pick a store, run doctor. [→ source](src/cli/commands/init.ts)
225
+ Lemon Squeezy ships API changes (new events, new fields, new resources) faster than client SDKs adopt them. fresh-squeezy tracks the [official changelog](https://docs.lemonsqueezy.com/api/getting-started/changelog) against a committed snapshot via a weekly GitHub Action, so newly recommended webhook events or response fields surface as actionable work instead of silently going unvalidated.
226
+
227
+ ### Can I use fresh-squeezy as a library instead of the CLI?
228
+
229
+ Yes. `import { createFreshSqueezy } from "fresh-squeezy"` and call `doctor()` or any individual validator. Every validator returns a typed, stable `ValidationResult` you can branch on — see [Library](#library).
230
+
231
+ ### Which Lemon Squeezy resources can it validate?
232
+
233
+ Connection/auth, stores, products (and variants), webhooks, discounts, license keys, and subscription plans. Add `--all-resources` to discover and validate every supported resource in the selected store(s). Full list in the [reference](#reference).
153
234
 
154
235
  ## Contributing
155
236
 
156
- See [CONTRIBUTING.md](./CONTRIBUTING.md). Clone, `npm install`, `npm test`.
237
+ See [CONTRIBUTING.md](./CONTRIBUTING.md). Clone, `npm install`, `npm test`. The project aims to stay small and boring — validator-first, one HTTP layer, stable `issue.code` contract.
238
+
239
+ ## Contributors
240
+
241
+ <a href="https://github.com/YosefHayim/fresh-squeezy/graphs/contributors">
242
+ <img src="https://contrib.rocks/image?repo=YosefHayim/fresh-squeezy" alt="fresh-squeezy contributors" />
243
+ </a>
157
244
 
158
245
  ## License
159
246
 
@@ -0,0 +1,249 @@
1
+ <!-- Traduzido de README.md; a versão em inglês é a fonte da verdade. -->
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/YosefHayim/fresh-squeezy">
5
+ <img src="assets/fresh-squeezy-hero.png" alt="fresh-squeezy — o doctor validator-first para a sua configuração do Lemon Squeezy. Detecte erros de configuração de cobrança e webhooks antes de subir para produção." width="640" />
6
+ </a>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <strong>O doctor para a sua configuração do Lemon Squeezy — detecte erros de configuração de cobrança e webhooks antes de subir para produção.</strong>
11
+ </p>
12
+
13
+ <!-- Badges. tests count is static; bump it on major test-suite changes. -->
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/fresh-squeezy"><img src="https://img.shields.io/npm/v/fresh-squeezy?logo=npm&amp;color=cb3837" alt="npm version" /></a>
16
+ <a href="https://www.npmjs.com/package/fresh-squeezy"><img src="https://img.shields.io/npm/dm/fresh-squeezy?logo=npm&amp;color=cb3837" alt="npm downloads per month" /></a>
17
+ <a href="https://github.com/YosefHayim/fresh-squeezy/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/YosefHayim/fresh-squeezy/ci.yml?branch=main&amp;logo=github&amp;label=CI" alt="CI status" /></a>
18
+ <a href="./LICENSE"><img src="https://img.shields.io/npm/l/fresh-squeezy?color=3fb950" alt="MIT license" /></a>
19
+ <img src="https://img.shields.io/node/v/fresh-squeezy?logo=node.js&amp;logoColor=white&amp;color=339933" alt="Node.js 20 or newer" />
20
+ <img src="https://img.shields.io/npm/types/fresh-squeezy?logo=typescript&amp;logoColor=white" alt="TypeScript types included" />
21
+ <a href="https://packagephobia.com/result?p=fresh-squeezy"><img src="https://packagephobia.com/badge?p=fresh-squeezy" alt="install size" /></a>
22
+ <img src="https://img.shields.io/badge/tests-154%20passing-3fb950?logo=vitest&amp;logoColor=white" alt="154 tests passing" />
23
+ </p>
24
+
25
+ <p align="center">
26
+ <a href="./README.md">English</a> ·
27
+ <a href="./README.zh-CN.md">简体中文</a> ·
28
+ <a href="./README.ja.md">日本語</a> ·
29
+ <a href="./README.es.md">Español</a> ·
30
+ <b>Português</b>
31
+ </p>
32
+
33
+ > As traduções podem estar desatualizadas. A versão canônica é o [README em inglês](./README.md).
34
+
35
+ <p align="center">
36
+ <a href="#início-rápido-em-30-segundos">Início rápido</a> ·
37
+ <a href="#o-que-ele-detecta-que-o-postman-e-o-sdk-oficial-não-detectam">O que ele detecta</a> ·
38
+ <a href="#fresh-squeezy-vs-as-alternativas">Comparação</a> ·
39
+ <a href="#cli">CLI</a> ·
40
+ <a href="#biblioteca">Biblioteca</a> ·
41
+ <a href="#códigos-de-problema">Códigos de problema</a> ·
42
+ <a href="#faq">FAQ</a>
43
+ </p>
44
+
45
+ ---
46
+
47
+ O **fresh-squeezy** é uma CLI e uma biblioteca TypeScript que valida a sua integração de cobrança do [Lemon Squeezy](https://www.lemonsqueezy.com/) — lojas, produtos, webhooks, descontos, chaves de licença e planos de assinatura — e detecta erros de configuração antes de subir para produção. Execute-o como um `doctor` de um único comando localmente ou em CI: ele retorna [códigos de saída](#início-rápido-em-30-segundos) estáveis e JSON legível por máquina, e acompanha o drift do [changelog da API do Lemon Squeezy](https://docs.lemonsqueezy.com/api/getting-started/changelog) que o SDK oficial ainda não incorporou. Node 20+.
48
+
49
+ ## Início rápido em 30 segundos
50
+
51
+ ```bash
52
+ npx fresh-squeezy
53
+ ```
54
+
55
+ A primeira execução adiciona `fresh-squeezy` às devDependencies quando ele está ausente e, em seguida, inicia a configuração guiada. Nenhum ID de loja para copiar do dashboard — a CLI descobre por conta própria as lojas acessíveis. Use `npx fresh-squeezy --no-install` para executar a configuração sem editar o `package.json`.
56
+
57
+ | Saída | Significado |
58
+ |------|---------|
59
+ | `0` | Todos os validadores passaram |
60
+ | `1` | Um ou mais validadores reportaram problemas de nível `error` |
61
+ | `2` | Fatal (chave ausente, flags inválidas, falha de rede) |
62
+ | `130` | O usuário cancelou um fluxo interativo |
63
+
64
+ ## O que ele detecta que o Postman e o SDK oficial não detectam
65
+
66
+ - **Chave de produção apontando para staging.** `MODE_MISMATCH` dispara quando o `meta.test_mode` real da chave (changelog da API 2024-01-05) diverge do modo declarado. O doctor sai com 1. Nem o SDK nem um wrapper feito à mão detectam isso por padrão.
67
+ - **Divergências silenciosas de propriedade de loja.** Produtos, descontos, chaves de licença e planos de assinatura cujo `store_id` não corresponde à loja para a qual você limitou a execução. Códigos estáveis: `PRODUCT_WRONG_STORE`, `DISCOUNT_STORE_MISMATCH`, `LICENSE_KEY_STORE_MISMATCH`, `PLAN_STORE_MISMATCH`.
68
+ - **Webhook inscrito nos eventos errados.** Diff em relação a um manifesto de eventos recomendados (ciclo de vida de pedidos/assinaturas, reembolsos) e eventos mais novos, porém opcionais, que o SDK não incorpora.
69
+ - **Drift da plataforma.** Uma GitHub Action semanal calcula o hash do [changelog da API do Lemon Squeezy](https://docs.lemonsqueezy.com/api/getting-started/changelog) em relação ao `src/support/changelog-snapshot.json`, atualiza os tipos de API derivados da documentação e abre trabalho de acompanhamento quando decisões de política são necessárias. As adições rastreadas incluem `customer_updated` (2026-02-25), `payment_processor` em Subscription (2025-06-11), Afiliados + `affiliate_activated` (2025-01-21), `quantity` em item de pedido (2024-12-06), estilização de checkout / `skip_trial` / `variant_quantities`, campos de reembolso de fatura de assinatura e `test_mode` em `/v1/users/me` (2024-01-05).
70
+ - **Ping-pong entre Postman e dashboard.** Uma única chamada `doctor` substitui o loop de copiar IDs da interface, colá-los em arquivos de env e verificar cada um manualmente.
71
+
72
+ ## fresh-squeezy vs as alternativas
73
+
74
+ Como uma verificação típica pré-deploy do Lemon Squeezy se compara entre as ferramentas que um desenvolvedor recorreria de outra forma:
75
+
76
+ | Recurso | fresh-squeezy | SDK oficial | Postman | Wrapper feito à mão |
77
+ |---|:---:|:---:|:---:|:---:|
78
+ | Detecção de divergência de modo / chave (`MODE_MISMATCH`) | ✅ | ❌ | ❌ | ❌ |
79
+ | Verificações cruzadas de propriedade de loja | ✅ | ❌ | ❌ | ⚠️ manual |
80
+ | Diff de cobertura de eventos de webhook | ✅ | ❌ | ⚠️ manual | ⚠️ manual |
81
+ | Validação de desconto / chave de licença / plano | ✅ | ❌ | ❌ | ⚠️ manual |
82
+ | Rastreamento de drift do changelog | ✅ | ❌ | ❌ | ❌ |
83
+ | Códigos de saída estáveis e prontos para CI + JSON | ✅ | ❌ | ❌ | ⚠️ manual |
84
+ | Varredura completa em um único comando (`doctor`) | ✅ | ❌ | ❌ | ❌ |
85
+ | Respostas de API tipadas | ✅ | ✅ | ❌ | ⚠️ depende |
86
+
87
+ O fresh-squeezy **não** é um substituto para o SDK oficial — é a verificação pré-voo que você executa *junto* dele. Use o SDK para fazer chamadas de API; use o fresh-squeezy para comprovar que a sua configuração está correta antes que essas chamadas cheguem à produção.
88
+
89
+ ## CLI
90
+
91
+ ```bash
92
+ # First run: install as a dev dependency, then start guided setup
93
+ npx fresh-squeezy
94
+
95
+ # Guided setup only: reuse env values, pick a store, choose resource checks
96
+ npx fresh-squeezy init
97
+
98
+ # TTY: multi-select stores interactively, run doctor on each
99
+ npx fresh-squeezy doctor
100
+
101
+ # Full sweep across every reachable store and resource
102
+ npx fresh-squeezy doctor --all-stores --all-resources
103
+
104
+ # Machine-readable full sweep for CI
105
+ npx fresh-squeezy doctor --all-stores --all-resources --json
106
+
107
+ # Single validator, scoped to specific stores
108
+ npx fresh-squeezy validate webhook \
109
+ --store-ids 12,34 \
110
+ --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
111
+ ```
112
+
113
+ As lojas são resolvidas nesta ordem para todo comando com escopo de loja: `--store-ids` explícito, depois `--all-stores`, depois uma seleção múltipla interativa em um TTY e, por fim, uma execução somente de conexão quando não há TTY nem flag (útil como smoke check de CI). O `doctor` valida a conexão e o acesso à loja, além de quaisquer flags de recurso explícitas; adicione `--all-resources` para descobrir e validar todos os recursos suportados na(s) loja(s) selecionada(s).
114
+
115
+ **→ Referência completa de comandos, flags e resolução de lojas: [docs/cli-reference.md](./docs/cli-reference.md)**
116
+
117
+ ## Biblioteca
118
+
119
+ ```ts
120
+ import { createFreshSqueezy } from "fresh-squeezy";
121
+
122
+ const lemon = createFreshSqueezy(); // reads LEMON_SQUEEZY_API_KEY, LEMON_SQUEEZY_MODE
123
+
124
+ const report = await lemon.doctor({
125
+ storeId: 12, // library is single-store per call
126
+ productId: 987,
127
+ webhookUrl: "https://app.example.com/api/webhooks/lemon-squeezy",
128
+ });
129
+
130
+ if (!report.ok) {
131
+ for (const result of report.results) {
132
+ for (const issue of result.issues) {
133
+ console.error(`[${issue.severity}] ${issue.code}: ${issue.message}`);
134
+ }
135
+ }
136
+ process.exit(1);
137
+ }
138
+ ```
139
+
140
+ Para execuções multi-loja na camada de biblioteca, chame `doctor()` em um loop — a CLI faz exatamente isso. Faça o branch com base em `issue.code` na lógica de CI; os códigos são estáveis entre versões menores.
141
+
142
+ Tipos públicos: [`FreshSqueezyClient`](src/createFreshSqueezy.ts), [`ValidationResult<T>`](src/core/types.ts), [`DoctorReport`](src/core/types.ts), interfaces de atributos de recurso em [`src/resources`](src/resources), tipos de objeto do Lemon Squeezy gerados a partir da documentação em [`src/generated/lemonSqueezyApiTypes.ts`](src/generated/lemonSqueezyApiTypes.ts) e helpers de augmentação do changelog em [`src/augmentations.ts`](src/augmentations.ts).
143
+
144
+ Para endpoints ainda não encapsulados, use a saída de emergência bruta:
145
+
146
+ ```ts
147
+ const user = await lemon.request({ path: "/v1/users/me" });
148
+ ```
149
+
150
+ ## Sandbox vs live
151
+
152
+ O Lemon Squeezy atende os dois modos a partir do mesmo host de API; o modo é determinado pela chave. O `fresh-squeezy` faz a verificação cruzada do modo declarado contra o `meta.test_mode` de `/v1/users/me`. Divergência = `MODE_MISMATCH`, o doctor sai com 1 — a forma mais rápida de detectar uma chave de produção apontando para staging antes que ela cause danos.
153
+
154
+ ```ts
155
+ const lemon = createFreshSqueezy({ mode: "test" });
156
+ const result = await lemon.validateConnection();
157
+ result.mode; // "test" (declared)
158
+ result.resource?.actualMode; // "live" — alarm bell
159
+ ```
160
+
161
+ O padrão da CLI é `--mode test`. Sobrescreva com `--mode live`. A configuração guiada pede confirmação explícita antes de continuar com uma chave em modo live detectada. Para verificações noturnas de drift da plataforma em CI, execute `npm run test:live` com `LEMON_SQUEEZY_LIVE_SMOKE=1` e uma chave em modo de teste.
162
+
163
+ ## Variáveis de ambiente
164
+
165
+ | Variável | Obrigatória | Finalidade |
166
+ |----------|----------|---------|
167
+ | `LEMON_SQUEEZY_API_KEY` | sim | Bearer token (biblioteca + CLI) |
168
+ | `LEMON_SQUEEZY_MODE` | não | `test` (padrão) ou `live` |
169
+ | `LEMON_SQUEEZY_STORE_ID` | não | Padrão de conveniência para `client.doctor()` — somente biblioteca |
170
+
171
+ A CLI não lê `LEMON_SQUEEZY_STORE_ID`; use `--store-ids` ou `--all-stores` para que a seleção de loja permaneça explícita em cada comando.
172
+
173
+ ## Códigos de problema
174
+
175
+ Faça o branch com base em `issue.code` na CI — todos os códigos são estáveis entre versões menores. Os mais comuns:
176
+
177
+ | Código | Significado |
178
+ |------|---------|
179
+ | `AUTH_FAILED` | Chave de API inválida ou ausente |
180
+ | `MODE_MISMATCH` | O modo declarado não corresponde ao `meta.test_mode` da chave |
181
+ | `STORE_NOT_FOUND` / `STORE_NOT_OWNED` | ID de loja inválido ou pertencente a outra conta |
182
+ | `PRODUCT_UNPUBLISHED` / `PRODUCT_WRONG_STORE` / `PRODUCT_NO_BUY_URL` | O produto não pode aceitar checkout |
183
+ | `WEBHOOK_NOT_FOUND` / `WEBHOOK_EVENTS_MISSING` | URL do webhook não registrada ou com inscrição insuficiente |
184
+
185
+ **→ Referência completa de códigos de problema (descontos, chaves de licença, planos, variantes, rede) com exemplos de saída de emergência: [docs/issue-codes.md](./docs/issue-codes.md)**
186
+
187
+ ## Referência
188
+
189
+ Validadores — cada um retorna um `ValidationResult` estável:
190
+
191
+ - **`validateConnection`** — acessibilidade, validade da chave, presença da loja, modo declarado vs. real. [→ código-fonte](src/validate/connection.ts)
192
+ - **`validateStore`** — o ID da loja existe e pertence à conta da chave. [→ código-fonte](src/validate/store.ts)
193
+ - **`validateProduct`** — publicado, na loja esperada, com variantes live e uma URL de compra. [→ código-fonte](src/validate/product.ts)
194
+ - **`validateWebhook`** — URL do webhook registrada e inscrita nos eventos recomendados. [→ código-fonte](src/validate/webhook.ts)
195
+ - **`validateDiscount`** — ativo, dentro da janela, valor válido, propriedade de loja correspondente. [→ código-fonte](src/validate/discount.ts)
196
+ - **`validateLicenseKey`** — habilitada, não expirada, ativações disponíveis, propriedade de loja correspondente. [→ código-fonte](src/validate/licenseKey.ts)
197
+ - **`validateSubscriptionPlan`** — tipo de assinatura, intervalo válido, preço não nulo, trial consistente. [→ código-fonte](src/validate/subscriptionPlan.ts)
198
+ - **`doctor`** — compõe os anteriores em um único `DoctorReport`. [→ código-fonte](src/validate/doctor.ts)
199
+
200
+ A cobertura de recursos é gerada a partir da documentação de objetos do Lemon Squeezy, então a maioria dos campos recém-documentados não precisa de edição manual:
201
+
202
+ ```bash
203
+ npm run generate:api-types
204
+ npm run check:api-types
205
+ ```
206
+
207
+ ## FAQ
208
+
209
+ ### Como verifico se o meu webhook do Lemon Squeezy está inscrito nos eventos certos?
210
+
211
+ Execute `npx fresh-squeezy validate webhook --store-ids <id> --webhook-url <url>`. O fresh-squeezy compara os eventos inscritos do seu webhook com um manifesto de eventos recomendados de pedido/assinatura/reembolso e reporta `WEBHOOK_EVENTS_MISSING` para as lacunas, ou `WEBHOOK_NOT_FOUND` se a URL não estiver registrada de forma alguma.
212
+
213
+ ### Como detecto uma chave de produção do Lemon Squeezy apontando para uma loja de teste (staging)?
214
+
215
+ Essa é a verificação `MODE_MISMATCH`. O fresh-squeezy compara o modo que você declarou (`--mode` ou `LEMON_SQUEEZY_MODE`) com o `meta.test_mode` real da chave de `/v1/users/me`. Quando eles divergem, o `doctor` sai com 1 — então uma chave live usada acidentalmente em um deploy de staging (ou vice-versa) falha na verificação antes de chegar aos usuários.
216
+
217
+ ### O fresh-squeezy funciona em CI?
218
+
219
+ Sim. Execute `npx fresh-squeezy doctor --all-stores --all-resources --json` para uma varredura completa legível por máquina. Ele retorna [códigos de saída](#início-rápido-em-30-segundos) estáveis (`0` passou, `1` erros de validação, `2` fatal) e strings `issue.code` estáveis nas quais você pode fazer asserções. Não requer TTY — sem flags de loja, ele recorre a um smoke check apenas de conexão.
220
+
221
+ ### O fresh-squeezy é um substituto para o SDK oficial do Lemon Squeezy?
222
+
223
+ Não. O [SDK oficial](https://github.com/lmsqueezy/lemonsqueezy.js) faz chamadas de API; o fresh-squeezy é a verificação pré-voo que comprova que a sua configuração está correta *antes* que essas chamadas cheguem à produção. Eles são complementares — veja a [tabela de comparação](#fresh-squeezy-vs-as-alternativas).
224
+
225
+ ### O que é "drift do changelog" e por que devo me importar?
226
+
227
+ O Lemon Squeezy lança mudanças de API (novos eventos, novos campos, novos recursos) mais rápido do que os SDKs clientes as adotam. O fresh-squeezy rastreia o [changelog oficial](https://docs.lemonsqueezy.com/api/getting-started/changelog) em relação a um snapshot versionado por meio de uma GitHub Action semanal, de modo que eventos de webhook ou campos de resposta recém-recomendados apareçam como trabalho acionável em vez de ficarem silenciosamente sem validação.
228
+
229
+ ### Posso usar o fresh-squeezy como biblioteca em vez da CLI?
230
+
231
+ Sim. `import { createFreshSqueezy } from "fresh-squeezy"` e chame `doctor()` ou qualquer validador individual. Todo validador retorna um `ValidationResult` tipado e estável no qual você pode fazer o branch — veja [Biblioteca](#biblioteca).
232
+
233
+ ### Quais recursos do Lemon Squeezy ele consegue validar?
234
+
235
+ Conexão/autenticação, lojas, produtos (e variantes), webhooks, descontos, chaves de licença e planos de assinatura. Adicione `--all-resources` para descobrir e validar todos os recursos suportados na(s) loja(s) selecionada(s). Lista completa na [referência](#referência).
236
+
237
+ ## Contribuindo
238
+
239
+ Veja [CONTRIBUTING.md](./CONTRIBUTING.md). Clone, `npm install`, `npm test`. O projeto busca permanecer pequeno e sem firulas — validator-first, uma única camada HTTP, contrato `issue.code` estável.
240
+
241
+ ## Contribuidores
242
+
243
+ <a href="https://github.com/YosefHayim/fresh-squeezy/graphs/contributors">
244
+ <img src="https://contrib.rocks/image?repo=YosefHayim/fresh-squeezy" alt="fresh-squeezy contributors" />
245
+ </a>
246
+
247
+ ## Licença
248
+
249
+ MIT — veja [LICENSE](./LICENSE).