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.es.md +249 -0
- package/README.ja.md +249 -0
- package/README.md +148 -61
- package/README.pt-BR.md +249 -0
- package/README.zh-CN.md +249 -0
- package/assets/fresh-squeezy-hero.png +0 -0
- package/dist/cli.js +1223 -160
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +357 -62
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +931 -40
- package/dist/index.d.ts +931 -40
- package/dist/index.js +334 -62
- package/dist/index.js.map +1 -1
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -1,63 +1,116 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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 & 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&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&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&logo=github&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&logoColor=white&color=339933" alt="Node.js 20 or newer" />
|
|
20
|
+
<img src="https://img.shields.io/npm/types/fresh-squeezy?logo=typescript&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&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
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
39
|
-
npx fresh-squeezy doctor --
|
|
102
|
+
# Machine-readable full sweep for CI
|
|
103
|
+
npx fresh-squeezy doctor --all-stores --all-resources --json
|
|
40
104
|
|
|
41
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
| `
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
##
|
|
205
|
+
## FAQ
|
|
126
206
|
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
219
|
+
### Is fresh-squeezy a replacement for the official Lemon Squeezy SDK?
|
|
138
220
|
|
|
139
|
-
|
|
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
|
-
###
|
|
223
|
+
### What is "changelog drift" and why should I care?
|
|
149
224
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
package/README.pt-BR.md
ADDED
|
@@ -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&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&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&logo=github&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&logoColor=white&color=339933" alt="Node.js 20 or newer" />
|
|
20
|
+
<img src="https://img.shields.io/npm/types/fresh-squeezy?logo=typescript&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&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).
|