fresh-squeezy 0.1.9 → 0.1.11

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,6 +1,48 @@
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
 
@@ -25,49 +67,50 @@ The first run adds `fresh-squeezy` to devDependencies when it is missing, then s
25
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).
26
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.
27
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
+
28
87
  ## CLI
29
88
 
30
89
  ```bash
31
90
  # First run: install as a dev dependency, then start guided setup
32
91
  npx fresh-squeezy
33
92
 
34
- # Guided setup only: reuse env values, pick a store, choose one or more resource checks
93
+ # Guided setup only: reuse env values, pick a store, choose resource checks
35
94
  npx fresh-squeezy init
36
95
 
37
96
  # TTY: multi-select stores interactively, run doctor on each
38
97
  npx fresh-squeezy doctor
39
98
 
40
- # Non-interactive: every reachable store
41
- npx fresh-squeezy doctor --all-stores
42
-
43
- # Full sweep: discover products, webhooks, discounts, license keys, and subscription plans
99
+ # Full sweep across every reachable store and resource
44
100
  npx fresh-squeezy doctor --all-stores --all-resources
45
101
 
46
- # Specific stores
47
- npx fresh-squeezy doctor --store-ids 12,34,56
48
-
49
- # Scope to a product + webhook
50
- npx fresh-squeezy doctor --store-ids 12 \
51
- --product-id 987 \
52
- --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
102
+ # Machine-readable full sweep for CI
103
+ npx fresh-squeezy doctor --all-stores --all-resources --json
53
104
 
54
- # Single validator
105
+ # Single validator, scoped to specific stores
55
106
  npx fresh-squeezy validate webhook \
56
107
  --store-ids 12,34 \
57
108
  --webhook-url https://app.example.com/api/webhooks/lemon-squeezy
58
-
59
- # Machine-readable full sweep for CI
60
- npx fresh-squeezy doctor --all-stores --all-resources --json
61
109
  ```
62
110
 
63
- Store resolution order, used by every store-scoped command:
64
-
65
- 1. `--store-ids 1,2,3` (comma-separated, explicit)
66
- 2. `--all-stores` (every store reachable with the key)
67
- 3. TTY: inquirer multi-select prompt
68
- 4. No TTY + no flag: connection-only run (useful as a CI smoke check)
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).
69
112
 
70
- By default, `doctor` validates connection and store access plus any explicit resource flags you pass. Add `--all-resources` when you want the CLI to discover and validate every supported resource in the selected store(s).
113
+ **→ Full command, flag, and store-resolution reference: [docs/cli-reference.md](./docs/cli-reference.md)**
71
114
 
72
115
  ## Library
73
116
 
@@ -92,15 +135,14 @@ if (!report.ok) {
92
135
  }
93
136
  ```
94
137
 
95
- 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.
96
139
 
97
- 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). Switch on `issue.code` in CI logic — codes are stable across minor versions.
98
- `ValidationResult.target` is optional and identifies the checked resource in human output and JSON when a validator has a product, webhook, store, discount, license key, or variant handle.
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).
99
141
 
100
- Generate a local augmentation file when your app uses the official SDK or older hand-rolled types:
142
+ For endpoints not yet wrapped, use the raw escape hatch:
101
143
 
102
- ```bash
103
- npx fresh-squeezy types augment
144
+ ```ts
145
+ const user = await lemon.request({ path: "/v1/users/me" });
104
146
  ```
105
147
 
106
148
  ## Sandbox vs live
@@ -114,87 +156,91 @@ result.mode; // "test" (declared)
114
156
  result.resource?.actualMode; // "live" — alarm bell
115
157
  ```
116
158
 
117
- 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.
118
- Guided setup asks for explicit confirmation before continuing with a detected live-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.
119
170
 
120
171
  ## Issue codes
121
172
 
122
- 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:
123
174
 
124
175
  | Code | Meaning |
125
176
  |------|---------|
126
177
  | `AUTH_FAILED` | Invalid or missing API key |
127
178
  | `MODE_MISMATCH` | Declared mode doesn't match key's `meta.test_mode` |
128
- | `NETWORK_ERROR` | Lemon Squeezy unreachable |
129
179
  | `STORE_NOT_FOUND` / `STORE_NOT_OWNED` | Store ID invalid or owned by another account |
130
180
  | `PRODUCT_UNPUBLISHED` / `PRODUCT_WRONG_STORE` / `PRODUCT_NO_BUY_URL` | Product can't accept checkout |
131
- | `VARIANT_MISSING` / `VARIANT_UNPUBLISHED` | Product has no live variants |
132
- | `WEBHOOK_NOT_FOUND` / `WEBHOOK_EVENTS_MISSING` / `WEBHOOK_OPTIONAL_EVENTS` | Webhook URL not registered or under-subscribed |
133
- | `DISCOUNT_DRAFT` / `DISCOUNT_EXPIRED` / `DISCOUNT_NOT_STARTED` / `DISCOUNT_INVALID_AMOUNT` / `DISCOUNT_REDEMPTIONS_EXHAUSTED` / `DISCOUNT_STORE_MISMATCH` | Discount won't apply at checkout |
134
- | `LICENSE_KEY_DISABLED` / `LICENSE_KEY_EXPIRED` / `LICENSE_KEY_AT_ACTIVATION_LIMIT` / `LICENSE_KEY_STORE_MISMATCH` | License key won't activate |
135
- | `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 |
136
182
 
137
- 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)**
138
184
 
139
- ```ts
140
- const user = await lemon.request({ path: "/v1/users/me" });
141
- ```
185
+ ## Reference
142
186
 
143
- For wrapped-but-not-validated endpoints, import the exported attribute type and use `request()`:
187
+ Validators — each returns a stable `ValidationResult`:
144
188
 
145
- ```ts
146
- import type { SubscriptionInvoiceAttributes } from "fresh-squeezy";
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)
147
197
 
148
- const invoice = await lemon.request<{ data: { attributes: SubscriptionInvoiceAttributes } }>({
149
- path: "/v1/subscription-invoices/123",
150
- });
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
151
203
  ```
152
204
 
153
- ## Environment variables
205
+ ## FAQ
154
206
 
155
- | Variable | Required | Purpose |
156
- |----------|----------|---------|
157
- | `LEMON_SQUEEZY_API_KEY` | yes | Bearer token (library + CLI) |
158
- | `LEMON_SQUEEZY_MODE` | no | `test` (default) or `live` |
159
- | `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?
160
208
 
161
- 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.
162
210
 
163
- ## Reference
211
+ ### How do I catch a Lemon Squeezy production key pointed at a test (staging) store?
164
212
 
165
- ### Validators
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.
166
214
 
167
- - **`validateConnection`** — Reachability, key validity, store presence, declared-vs-actual mode. [→ source](src/validate/connection.ts)
168
- - **`validateStore`** — Store ID exists and is owned by the key's account. [→ source](src/validate/store.ts)
169
- - **`validateProduct`** — Published, on the expected store, has live variants and a buy URL. [→ source](src/validate/product.ts)
170
- - **`validateWebhook`** — Webhook URL registered and subscribed to recommended events. [→ source](src/validate/webhook.ts)
171
- - **`validateDiscount`** — Active, in-window, valid amount, store ownership matches. [→ source](src/validate/discount.ts)
172
- - **`validateLicenseKey`** — Enabled, not expired, activations available, store ownership matches. [→ source](src/validate/licenseKey.ts)
173
- - **`validateSubscriptionPlan`** — Subscription type, valid interval, non-zero price, consistent trial. [→ source](src/validate/subscriptionPlan.ts)
174
- - **`doctor`** — Composes the above into one `DoctorReport`. [→ source](src/validate/doctor.ts)
175
- - **Types/resources** — Changelog-backed interfaces for customers, checkouts, order items, subscription invoices/items, usage records, affiliates, and more. [→ source](src/resources)
215
+ ### Does fresh-squeezy work in CI?
176
216
 
177
- ### CLI commands
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.
178
218
 
179
- - **`doctor`** — Run configured validators; add `--all-resources` for discovery-backed full sweeps. [→ source](src/cli/commands/doctor.ts)
180
- - **`validate <name>`** — Run a single validator. [→ source](src/cli/commands/validate.ts)
181
- - **`init`** — Interactive setup: ask for credentials, pick a store, run doctor. [→ source](src/cli/commands/init.ts)
182
- - **`types augment`** — Emit a local `.d.ts` for changelog fields not present in older SDK/local types. [→ source](src/cli/commands/augment.ts)
219
+ ### Is fresh-squeezy a replacement for the official Lemon Squeezy SDK?
183
220
 
184
- ## Keeping API Types Current
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).
185
222
 
186
- Resource coverage is generated from Lemon Squeezy's object docs, so most newly documented fields do not require a hand edit:
223
+ ### What is "changelog drift" and why should I care?
187
224
 
188
- ```bash
189
- npm run generate:api-types
190
- npm run check:api-types
191
- ```
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).
192
230
 
193
- Manual review still applies to validator behavior and webhook policy: new events belong in `RECOMMENDED_WEBHOOK_EVENTS` or `OPTIONAL_WEBHOOK_EVENTS`, and meaningful platform changes should be acknowledged in `ACKNOWLEDGED_CHANGELOG_ENTRIES`.
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).
194
234
 
195
235
  ## Contributing
196
236
 
197
- 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>
198
244
 
199
245
  ## License
200
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).