store-fn 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +419 -0
- package/debug.ts +23 -0
- package/dist/cli.js +21 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
- package/vite-env.d.ts +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# store-fn
|
|
2
|
+
|
|
3
|
+
A simple utility function to define and sync Polar products using code. Define your products in TypeScript and keep them in sync with your Polar store.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install store-fn
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Create a store configuration file
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// store.config.ts
|
|
17
|
+
|
|
18
|
+
import { Polar } from "@polar-sh/sdk"
|
|
19
|
+
import { createStoreFn } from "store-fn"
|
|
20
|
+
|
|
21
|
+
const polarClient = new Polar({
|
|
22
|
+
accessToken: process.env.POLAR_API_KEY!,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const store = createStoreFn({
|
|
26
|
+
client: polarClient,
|
|
27
|
+
organizationId: process.env.POLAR_ORGANIZATION_ID!,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Define your products here
|
|
31
|
+
store.defineProduct({
|
|
32
|
+
key: "basic",
|
|
33
|
+
name: "Basic Plan",
|
|
34
|
+
description: "Get started with the essentials.",
|
|
35
|
+
recurringInterval: "month",
|
|
36
|
+
recurringIntervalCount: 1,
|
|
37
|
+
prices: [
|
|
38
|
+
{
|
|
39
|
+
amountType: "fixed",
|
|
40
|
+
priceAmount: 9.99, // Automatically converted to 999 cents
|
|
41
|
+
priceCurrency: "usd",
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
metadata: {
|
|
45
|
+
maxUsers: 5,
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export default store
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 2. Sync products to Polar
|
|
53
|
+
|
|
54
|
+
You can sync products programmatically or using the CLI.
|
|
55
|
+
|
|
56
|
+
**Programmatically:**
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import store from "./store.config.js"
|
|
60
|
+
|
|
61
|
+
// Sync all defined products to Polar
|
|
62
|
+
const result = await store.push()
|
|
63
|
+
console.log(`Synced ${result.updatedProducts.length} products`)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Using CLI:**
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
store-fn push -i store.config.ts -o products.ts
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## API Reference
|
|
74
|
+
|
|
75
|
+
### `createStoreFn(options)`
|
|
76
|
+
|
|
77
|
+
Creates a store instance for defining and syncing products.
|
|
78
|
+
|
|
79
|
+
**Parameters:**
|
|
80
|
+
- `options.client` - A Polar SDK client instance
|
|
81
|
+
- `options.organizationId` - Your Polar organization ID
|
|
82
|
+
|
|
83
|
+
**Returns:** An object with `defineProduct` and `push` methods
|
|
84
|
+
|
|
85
|
+
### `store.defineProduct(definition)`
|
|
86
|
+
|
|
87
|
+
Defines a product. Products are identified by their `key` in metadata.
|
|
88
|
+
|
|
89
|
+
**Parameters:**
|
|
90
|
+
- `definition.key` - Unique identifier for the product (stored in metadata)
|
|
91
|
+
- `definition.name` - Product name
|
|
92
|
+
- `definition.description` - Product description
|
|
93
|
+
- `definition.recurringInterval` - Optional: `"month"` | `"year"` | `"week"` | `"day"`
|
|
94
|
+
- `definition.recurringIntervalCount` - Optional: Number of intervals (default: 1)
|
|
95
|
+
- `definition.prices` - Array of price definitions
|
|
96
|
+
- `definition.metadata` - Optional: Custom metadata object
|
|
97
|
+
- `definition.virtual` - Optional: Set to `true` for virtual products (not synced to Polar)
|
|
98
|
+
- `definition.id` - Required if `virtual: true`
|
|
99
|
+
|
|
100
|
+
**Returns:** The product definition (with prices converted to cents)
|
|
101
|
+
|
|
102
|
+
### `store.push()`
|
|
103
|
+
|
|
104
|
+
Syncs all defined products to Polar. Creates new products or updates existing ones based on the `key` in metadata.
|
|
105
|
+
|
|
106
|
+
**Returns:** `Promise<{ updatedProducts: Product[] }>`
|
|
107
|
+
|
|
108
|
+
### `writeProductsToFile(products, path)`
|
|
109
|
+
|
|
110
|
+
Utility function to write products to a TypeScript file. Used internally by the CLI.
|
|
111
|
+
|
|
112
|
+
**Parameters:**
|
|
113
|
+
- `products` - Array of Product objects
|
|
114
|
+
- `path` - File path where products will be written
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## Usage Examples
|
|
118
|
+
|
|
119
|
+
### Basic Product with Fixed Pricing
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
store.defineProduct({
|
|
123
|
+
key: "basic",
|
|
124
|
+
name: "Basic Plan",
|
|
125
|
+
description: "Get started with the essentials.",
|
|
126
|
+
prices: [
|
|
127
|
+
{
|
|
128
|
+
amountType: "fixed",
|
|
129
|
+
priceAmount: 9.99, // Converts to 999 cents
|
|
130
|
+
priceCurrency: "usd",
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Recurring Subscription
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
store.defineProduct({
|
|
140
|
+
key: "pro-monthly",
|
|
141
|
+
name: "Pro Plan",
|
|
142
|
+
description: "Monthly subscription for professionals.",
|
|
143
|
+
recurringInterval: "month",
|
|
144
|
+
recurringIntervalCount: 1,
|
|
145
|
+
prices: [
|
|
146
|
+
{
|
|
147
|
+
amountType: "fixed",
|
|
148
|
+
priceAmount: 29.99,
|
|
149
|
+
priceCurrency: "usd",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Annual Subscription
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
store.defineProduct({
|
|
159
|
+
key: "pro-yearly",
|
|
160
|
+
name: "Pro Plan (Annual)",
|
|
161
|
+
description: "Annual subscription with 2 months free.",
|
|
162
|
+
recurringInterval: "year",
|
|
163
|
+
recurringIntervalCount: 1,
|
|
164
|
+
prices: [
|
|
165
|
+
{
|
|
166
|
+
amountType: "fixed",
|
|
167
|
+
priceAmount: 299.99, // $29.99/month * 10 months
|
|
168
|
+
priceCurrency: "usd",
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Custom Pricing (Pay What You Want)
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
store.defineProduct({
|
|
178
|
+
key: "custom-donation",
|
|
179
|
+
name: "Custom Donation",
|
|
180
|
+
description: "Support us with any amount.",
|
|
181
|
+
prices: [
|
|
182
|
+
{
|
|
183
|
+
amountType: "custom",
|
|
184
|
+
presetAmount: 25.00, // Suggested amount
|
|
185
|
+
minimumAmount: 5.00, // Minimum allowed
|
|
186
|
+
maximumAmount: 1000.00, // Maximum allowed
|
|
187
|
+
priceCurrency: "usd",
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Free Product
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
store.defineProduct({
|
|
197
|
+
key: "free-tier",
|
|
198
|
+
name: "Free Plan",
|
|
199
|
+
description: "Forever free, no credit card required.",
|
|
200
|
+
prices: [
|
|
201
|
+
{
|
|
202
|
+
amountType: "free",
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
})
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Seat-Based Pricing
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
store.defineProduct({
|
|
212
|
+
key: "team-plan",
|
|
213
|
+
name: "Team Plan",
|
|
214
|
+
description: "Perfect for teams of all sizes.",
|
|
215
|
+
prices: [
|
|
216
|
+
{
|
|
217
|
+
amountType: "seat_based",
|
|
218
|
+
priceCurrency: "usd",
|
|
219
|
+
seatTiers: [
|
|
220
|
+
{
|
|
221
|
+
upTo: 5,
|
|
222
|
+
unitAmount: 10.00, // $10 per seat for first 5 seats
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
upTo: 20,
|
|
226
|
+
unitAmount: 8.00, // $8 per seat for seats 6-20
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
upTo: null, // Unlimited
|
|
230
|
+
unitAmount: 5.00, // $5 per seat for 21+
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
})
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Metered/Usage-Based Pricing
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
store.defineProduct({
|
|
242
|
+
key: "usage-based",
|
|
243
|
+
name: "Pay As You Go",
|
|
244
|
+
description: "Pay only for what you use.",
|
|
245
|
+
prices: [
|
|
246
|
+
{
|
|
247
|
+
amountType: "metered_unit",
|
|
248
|
+
priceCurrency: "usd",
|
|
249
|
+
capAmount: 1000.00, // Optional: maximum charge per billing period
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
})
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Product with Custom Metadata
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
store.defineProduct({
|
|
259
|
+
key: "enterprise",
|
|
260
|
+
name: "Enterprise Plan",
|
|
261
|
+
description: "For large organizations.",
|
|
262
|
+
recurringInterval: "month",
|
|
263
|
+
recurringIntervalCount: 1,
|
|
264
|
+
prices: [
|
|
265
|
+
{
|
|
266
|
+
amountType: "fixed",
|
|
267
|
+
priceAmount: 499.99,
|
|
268
|
+
priceCurrency: "usd",
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
metadata: {
|
|
272
|
+
maxUsers: 1000,
|
|
273
|
+
features: ["priority-support", "custom-integrations", "sla"],
|
|
274
|
+
trialDays: 30,
|
|
275
|
+
},
|
|
276
|
+
})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Virtual Products (Not Synced to Polar)
|
|
280
|
+
|
|
281
|
+
Virtual products are useful for local development or products that exist only in your application:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
store.defineProduct({
|
|
285
|
+
virtual: true,
|
|
286
|
+
key: "local-test",
|
|
287
|
+
name: "Local Test Product",
|
|
288
|
+
description: "Only exists locally, not synced to Polar.",
|
|
289
|
+
prices: [
|
|
290
|
+
{
|
|
291
|
+
amountType: "free",
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
})
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Multiple Products
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
// Basic tier
|
|
301
|
+
store.defineProduct({
|
|
302
|
+
key: "basic",
|
|
303
|
+
name: "Basic Plan",
|
|
304
|
+
description: "Perfect for individuals.",
|
|
305
|
+
recurringInterval: "month",
|
|
306
|
+
recurringIntervalCount: 1,
|
|
307
|
+
prices: [
|
|
308
|
+
{
|
|
309
|
+
amountType: "fixed",
|
|
310
|
+
priceAmount: 9.99,
|
|
311
|
+
priceCurrency: "usd",
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
// Pro tier
|
|
317
|
+
store.defineProduct({
|
|
318
|
+
key: "pro",
|
|
319
|
+
name: "Pro Plan",
|
|
320
|
+
description: "For professionals and small teams.",
|
|
321
|
+
recurringInterval: "month",
|
|
322
|
+
recurringIntervalCount: 1,
|
|
323
|
+
prices: [
|
|
324
|
+
{
|
|
325
|
+
amountType: "fixed",
|
|
326
|
+
priceAmount: 29.99,
|
|
327
|
+
priceCurrency: "usd",
|
|
328
|
+
},
|
|
329
|
+
],
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// Enterprise tier
|
|
333
|
+
store.defineProduct({
|
|
334
|
+
key: "enterprise",
|
|
335
|
+
name: "Enterprise Plan",
|
|
336
|
+
description: "For large organizations.",
|
|
337
|
+
recurringInterval: "month",
|
|
338
|
+
recurringIntervalCount: 1,
|
|
339
|
+
prices: [
|
|
340
|
+
{
|
|
341
|
+
amountType: "fixed",
|
|
342
|
+
priceAmount: 99.99,
|
|
343
|
+
priceCurrency: "usd",
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
metadata: {
|
|
347
|
+
includesSupport: true,
|
|
348
|
+
maxUsers: 1000,
|
|
349
|
+
},
|
|
350
|
+
})
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## CLI Usage
|
|
354
|
+
|
|
355
|
+
The CLI allows you to sync products and generate a TypeScript file with all your products.
|
|
356
|
+
|
|
357
|
+
### Commands
|
|
358
|
+
|
|
359
|
+
#### `push` - Sync products to Polar and generate products file
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
store-fn push -i <input-file> -o <output-file>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Options:**
|
|
366
|
+
- `-i` - Path to your store definition file (default: `store.config.ts`)
|
|
367
|
+
- `-o` - Path where the generated products file will be written (default: `store/products.ts`)
|
|
368
|
+
|
|
369
|
+
**Example:**
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
store-fn push -i store.config.ts -o products.ts
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
This command will:
|
|
376
|
+
1. Load your store definition file
|
|
377
|
+
2. Sync all products to Polar (create new ones or update existing ones)
|
|
378
|
+
3. Generate a TypeScript file with all synced products
|
|
379
|
+
|
|
380
|
+
### Generated Products File
|
|
381
|
+
|
|
382
|
+
After running `push`, you'll get a TypeScript file with all your products:
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
// products.ts (generated)
|
|
386
|
+
|
|
387
|
+
import type { Product } from "@polar-sh/sdk/models/components/product.js"
|
|
388
|
+
|
|
389
|
+
export const basicPlanProduct = {
|
|
390
|
+
id: "prod_123...",
|
|
391
|
+
name: "Basic Plan",
|
|
392
|
+
// ... full product object
|
|
393
|
+
} as const satisfies Product
|
|
394
|
+
|
|
395
|
+
export const proPlanProduct = {
|
|
396
|
+
id: "prod_456...",
|
|
397
|
+
name: "Pro Plan",
|
|
398
|
+
// ... full product object
|
|
399
|
+
} as const satisfies Product
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
You can then import and use these products in your application:
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
import { basicPlanProduct, proPlanProduct } from "./products.js"
|
|
406
|
+
|
|
407
|
+
// Use the product ID
|
|
408
|
+
const productId = basicPlanProduct.id
|
|
409
|
+
|
|
410
|
+
// Access product details
|
|
411
|
+
const price = basicPlanProduct.prices[0]
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
## Notes
|
|
415
|
+
|
|
416
|
+
- **Price Conversion**: All price amounts are automatically converted from dollars to cents (multiplied by 100)
|
|
417
|
+
- **Product Identification**: Products are matched by the `key` field in metadata. If a product with the same key exists, it will be updated; otherwise, a new product will be created
|
|
418
|
+
- **Virtual Products**: Products marked as `virtual: true` are not synced to Polar but are included in the generated products file
|
|
419
|
+
- **Type Safety**: The generated products file includes TypeScript types for full type safety
|
package/debug.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// import { URLPattern } from "urlpattern-polyfill/urlpattern"
|
|
2
|
+
// import { safeUrl } from "./src/safe-url"
|
|
3
|
+
|
|
4
|
+
import { createRouteFn } from "./src"
|
|
5
|
+
|
|
6
|
+
// const pattern = new URLPattern({ pathname: safeUrl("/user/:id/settings/:page*") })
|
|
7
|
+
// console.log(pattern.test("http://localhost:3000/user/123/settings/456"))
|
|
8
|
+
// console.log(pattern.test("http://localhost:3000/user/123/settings/456/"))
|
|
9
|
+
// console.log(pattern.test("http://localhost:3000/user/123/settings/456/extra"))
|
|
10
|
+
const route = createRouteFn([
|
|
11
|
+
"/:org/:project/settings",
|
|
12
|
+
"/:org/:project/posts",
|
|
13
|
+
"/:org/:project",
|
|
14
|
+
"/:org/settings/billing",
|
|
15
|
+
"/:org/settings",
|
|
16
|
+
"/:org",
|
|
17
|
+
])
|
|
18
|
+
|
|
19
|
+
// console.log(route.matchUrl("/apple", ["/:org", "/:org/settings", "/:org/settings/billing"]))
|
|
20
|
+
console.log(route.matchUrl("/apple/settings", "/:org/*"))
|
|
21
|
+
// console.log(
|
|
22
|
+
// route.matchUrl("/apple/settings/billing", ["/:org", "/:org/settings", "/:org/settings/billing"])
|
|
23
|
+
// )
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
import s from"picocolors";import{resolve as f}from"path";function p(e){return e&&e.split(/[\s-_]+/).map((r,n)=>(r=r.toLowerCase(),n>0?r.charAt(0).toUpperCase()+r.slice(1):r)).join("")}function h(e){let r=JSON.stringify(e,null,2).replace(/"(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ)"/g,n=>`new Date(${n})`);return`export const ${p(e.name)}Product = ${r} as const satisfies Product`}async function g(e,r){if(typeof window<"u")throw new Error("writeProductsToFile is not supported in the browser");let n=`
|
|
4
|
+
import type { Product } from "@polar-sh/sdk/models/components/product.js"
|
|
5
|
+
|
|
6
|
+
${e.map(h).join(`
|
|
7
|
+
|
|
8
|
+
`)}
|
|
9
|
+
`,t=await import("fs/promises").then(i=>i.writeFile),a=await import("prettier").then(i=>i.default).catch(()=>null);if(a){let i=await import("path").then(o=>o.default),m=await import("fs").then(o=>o.existsSync),l=i.dirname(i.resolve(import.meta.url)),c=["prettier.config.js","prettier.config.ts"].map(o=>i.resolve(l,o)).find(o=>m(o)),u=c?await import(c).then(o=>o.default).catch(()=>null):null;n=await a.format(n.trim(),{parser:"typescript",...u})}await t(r,n)}async function y(){let e=process.argv.slice(2),r=e[0];r||(console.error(s.red("Error: No command provided")),console.log(`
|
|
10
|
+
Usage: store-fn <command>`),console.log(`
|
|
11
|
+
Commands:`),console.log(" push Sync products to Polar store"),console.log(`
|
|
12
|
+
Example:`),console.log(" store-fn push -i store.config.ts -o store/products.ts"),console.log(" store-fn push # Uses defaults: -i store.config.ts -o store/products.ts"),process.exit(1)),r==="push"?await P(e.slice(1)):(console.error(s.red(`Error: Unknown command "${r}"`)),console.log(`
|
|
13
|
+
Available commands:`),console.log(" push Sync products to Polar store"),process.exit(1))}async function P(e){let r,n;for(let t=0;t<e.length;t++)e[t]==="-i"&&t+1<e.length?(r=e[t+1],t++):e[t]==="-o"&&t+1<e.length&&(n=e[t+1],t++);r=r||"store.config.ts",n=n||"store/products.ts";try{let t=f(process.cwd(),r),a=f(process.cwd(),n);console.log(s.blue(`Loading store definition from: ${t}`));let i=f(t),m=i.startsWith("file://")?i:`file://${i}`,l;try{l=await import(m)}catch(o){throw(o.code==="ERR_UNKNOWN_FILE_EXTENSION"||o.message?.includes("Unknown file extension"))&&t.endsWith(".ts")?new Error(`Cannot load TypeScript file: ${t}
|
|
14
|
+
Please run this CLI using tsx: tsx src/cli.ts push -i ${r} -o ${n}
|
|
15
|
+
Or compile your store file to JavaScript first.`):o.code==="ERR_MODULE_NOT_FOUND"||o.code==="ENOENT"?new Error(`Store file not found: ${t}
|
|
16
|
+
Make sure the file exists and the path is correct.`):o.message?.includes("Cannot find module")?new Error(`Cannot load store file: ${t}
|
|
17
|
+
If it's a TypeScript file, make sure you're using tsx to run the CLI or compile it first.`):o}let d=l.default;if(!d)throw new Error(`Store file must have a default export. Found exports: ${Object.keys(l).join(", ")}`);if(typeof d.push!="function")throw new Error("Store default export must be the return value of createStoreFn (an object with a push function)");console.log(s.blue(`Syncing products to Polar store...
|
|
18
|
+
`));let c=await d.push();if(!c||!c.updatedProducts)throw new Error("Push function did not return products. Expected { updatedProducts: Product[] }");let u=c.updatedProducts;if(!Array.isArray(u)||u.length===0){console.log(s.yellow("No products to write"));return}console.log(s.blue(`
|
|
19
|
+
Writing ${u.length} product(s) to: ${a}`)),await g(u,a),console.log(s.green(`
|
|
20
|
+
\u2713 Successfully wrote products to ${a}`))}catch(t){console.error(s.red(`
|
|
21
|
+
Error: ${t.message}`)),t.stack&&process.env.DEBUG&&console.error(t.stack),process.exit(1)}}y().catch(e=>{console.error(s.red(`Fatal error: ${e.message}`)),e.stack&&console.error(e.stack),process.exit(1)});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as _polar_sh_sdk_models_components_subscriptionrecurringinterval_js from '@polar-sh/sdk/models/components/subscriptionrecurringinterval.js';
|
|
2
|
+
import * as _polar_sh_sdk_models_components_trialinterval_js from '@polar-sh/sdk/models/components/trialinterval.js';
|
|
3
|
+
import * as _polar_sh_sdk_models_components_attachedcustomfieldcreate_js from '@polar-sh/sdk/models/components/attachedcustomfieldcreate.js';
|
|
4
|
+
import * as _polar_sh_sdk_models_components_productpriceseatbasedcreate_js from '@polar-sh/sdk/models/components/productpriceseatbasedcreate.js';
|
|
5
|
+
import * as _polar_sh_sdk_models_components_productpricemeteredunitcreate_js from '@polar-sh/sdk/models/components/productpricemeteredunitcreate.js';
|
|
6
|
+
import * as _polar_sh_sdk_models_components_productpricefreecreate_js from '@polar-sh/sdk/models/components/productpricefreecreate.js';
|
|
7
|
+
import * as _polar_sh_sdk_models_components_productpricefixedcreate_js from '@polar-sh/sdk/models/components/productpricefixedcreate.js';
|
|
8
|
+
import * as _polar_sh_sdk_models_components_productpricecustomcreate_js from '@polar-sh/sdk/models/components/productpricecustomcreate.js';
|
|
9
|
+
import { Polar } from '@polar-sh/sdk';
|
|
10
|
+
import { ProductCreate } from '@polar-sh/sdk/models/components/productcreate.js';
|
|
11
|
+
import { Product } from '@polar-sh/sdk/models/components/product.js';
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
var polarClient: Polar;
|
|
15
|
+
var polarOrganizationId: string;
|
|
16
|
+
}
|
|
17
|
+
type ProductDefinition<TMeta> = ProductCreate & {
|
|
18
|
+
key: string;
|
|
19
|
+
metadata?: TMeta;
|
|
20
|
+
} & ({
|
|
21
|
+
virtual?: false | undefined;
|
|
22
|
+
} | {
|
|
23
|
+
virtual: true;
|
|
24
|
+
id: string;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
declare function writeProductsToFile(products: Product[], path: string): Promise<void>;
|
|
28
|
+
|
|
29
|
+
interface CreateStoreFnOptions {
|
|
30
|
+
client: Polar;
|
|
31
|
+
organizationId: string;
|
|
32
|
+
}
|
|
33
|
+
declare function createStoreFn(options: CreateStoreFnOptions): {
|
|
34
|
+
defineProduct: <TMeta extends ProductCreate["metadata"]>({ key, ...details }: ProductDefinition<TMeta>) => {
|
|
35
|
+
metadata: TMeta & {
|
|
36
|
+
key: string;
|
|
37
|
+
};
|
|
38
|
+
name: string;
|
|
39
|
+
description?: string | null | undefined;
|
|
40
|
+
prices: Array<_polar_sh_sdk_models_components_productpricecustomcreate_js.ProductPriceCustomCreate | _polar_sh_sdk_models_components_productpricefixedcreate_js.ProductPriceFixedCreate | _polar_sh_sdk_models_components_productpricefreecreate_js.ProductPriceFreeCreate | _polar_sh_sdk_models_components_productpricemeteredunitcreate_js.ProductPriceMeteredUnitCreate | _polar_sh_sdk_models_components_productpriceseatbasedcreate_js.ProductPriceSeatBasedCreate>;
|
|
41
|
+
medias?: Array<string> | null | undefined;
|
|
42
|
+
attachedCustomFields?: Array<_polar_sh_sdk_models_components_attachedcustomfieldcreate_js.AttachedCustomFieldCreate> | undefined;
|
|
43
|
+
organizationId?: string | null | undefined;
|
|
44
|
+
trialInterval?: _polar_sh_sdk_models_components_trialinterval_js.TrialInterval | null | undefined;
|
|
45
|
+
trialIntervalCount?: number | null | undefined;
|
|
46
|
+
recurringInterval: _polar_sh_sdk_models_components_subscriptionrecurringinterval_js.SubscriptionRecurringInterval;
|
|
47
|
+
recurringIntervalCount?: number | undefined;
|
|
48
|
+
virtual?: false | undefined;
|
|
49
|
+
} | {
|
|
50
|
+
metadata: TMeta & {
|
|
51
|
+
key: string;
|
|
52
|
+
};
|
|
53
|
+
name: string;
|
|
54
|
+
description?: string | null | undefined;
|
|
55
|
+
prices: Array<_polar_sh_sdk_models_components_productpricecustomcreate_js.ProductPriceCustomCreate | _polar_sh_sdk_models_components_productpricefixedcreate_js.ProductPriceFixedCreate | _polar_sh_sdk_models_components_productpricefreecreate_js.ProductPriceFreeCreate | _polar_sh_sdk_models_components_productpricemeteredunitcreate_js.ProductPriceMeteredUnitCreate | _polar_sh_sdk_models_components_productpriceseatbasedcreate_js.ProductPriceSeatBasedCreate>;
|
|
56
|
+
medias?: Array<string> | null | undefined;
|
|
57
|
+
attachedCustomFields?: Array<_polar_sh_sdk_models_components_attachedcustomfieldcreate_js.AttachedCustomFieldCreate> | undefined;
|
|
58
|
+
organizationId?: string | null | undefined;
|
|
59
|
+
trialInterval?: _polar_sh_sdk_models_components_trialinterval_js.TrialInterval | null | undefined;
|
|
60
|
+
trialIntervalCount?: number | null | undefined;
|
|
61
|
+
recurringInterval: _polar_sh_sdk_models_components_subscriptionrecurringinterval_js.SubscriptionRecurringInterval;
|
|
62
|
+
recurringIntervalCount?: number | undefined;
|
|
63
|
+
virtual: true;
|
|
64
|
+
id: string;
|
|
65
|
+
} | {
|
|
66
|
+
metadata: TMeta & {
|
|
67
|
+
key: string;
|
|
68
|
+
};
|
|
69
|
+
name: string;
|
|
70
|
+
description?: string | null | undefined;
|
|
71
|
+
prices: Array<_polar_sh_sdk_models_components_productpricecustomcreate_js.ProductPriceCustomCreate | _polar_sh_sdk_models_components_productpricefixedcreate_js.ProductPriceFixedCreate | _polar_sh_sdk_models_components_productpricefreecreate_js.ProductPriceFreeCreate | _polar_sh_sdk_models_components_productpricemeteredunitcreate_js.ProductPriceMeteredUnitCreate | _polar_sh_sdk_models_components_productpriceseatbasedcreate_js.ProductPriceSeatBasedCreate>;
|
|
72
|
+
medias?: Array<string> | null | undefined;
|
|
73
|
+
attachedCustomFields?: Array<_polar_sh_sdk_models_components_attachedcustomfieldcreate_js.AttachedCustomFieldCreate> | undefined;
|
|
74
|
+
organizationId?: string | null | undefined;
|
|
75
|
+
recurringInterval?: any | null | undefined;
|
|
76
|
+
recurringIntervalCount?: any | null | undefined;
|
|
77
|
+
virtual?: false | undefined;
|
|
78
|
+
} | {
|
|
79
|
+
metadata: TMeta & {
|
|
80
|
+
key: string;
|
|
81
|
+
};
|
|
82
|
+
name: string;
|
|
83
|
+
description?: string | null | undefined;
|
|
84
|
+
prices: Array<_polar_sh_sdk_models_components_productpricecustomcreate_js.ProductPriceCustomCreate | _polar_sh_sdk_models_components_productpricefixedcreate_js.ProductPriceFixedCreate | _polar_sh_sdk_models_components_productpricefreecreate_js.ProductPriceFreeCreate | _polar_sh_sdk_models_components_productpricemeteredunitcreate_js.ProductPriceMeteredUnitCreate | _polar_sh_sdk_models_components_productpriceseatbasedcreate_js.ProductPriceSeatBasedCreate>;
|
|
85
|
+
medias?: Array<string> | null | undefined;
|
|
86
|
+
attachedCustomFields?: Array<_polar_sh_sdk_models_components_attachedcustomfieldcreate_js.AttachedCustomFieldCreate> | undefined;
|
|
87
|
+
organizationId?: string | null | undefined;
|
|
88
|
+
recurringInterval?: any | null | undefined;
|
|
89
|
+
recurringIntervalCount?: any | null | undefined;
|
|
90
|
+
virtual: true;
|
|
91
|
+
id: string;
|
|
92
|
+
};
|
|
93
|
+
push: () => Promise<{
|
|
94
|
+
updatedProducts: Product[];
|
|
95
|
+
}>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export { createStoreFn, writeProductsToFile };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
async function m(e){let t=[],i=await e;for await(let c of i)t.push(...c.result.items);return t}function f(e){return e&&e.split(/[\s-_]+/).map((t,i)=>(t=t.toLowerCase(),i>0?t.charAt(0).toUpperCase()+t.slice(1):t)).join("")}async function p(){let[e,t]=await Promise.all([m(globalThis.polarClient.products.list({organizationId:globalThis.polarOrganizationId})),m(globalThis.polarClient.benefits.list({organizationId:globalThis.polarOrganizationId}))]);return{products:e,benefits:t}}function g(e){return{...e,description:e.description??"",metadata:e.metadata??{},isRecurring:!!e.recurringInterval,attachedCustomFields:[],benefits:[],medias:(e.medias??[]).map(t=>({id:crypto.randomUUID(),organizationId:"",name:"",path:t,mimeType:"",size:0,storageVersion:null,checksumEtag:null,checksumSha256Base64:null,checksumSha256Hex:null,version:null,service:"product_media",lastModifiedAt:null,isUploaded:!1,createdAt:new Date,sizeReadable:"0 kb",publicUrl:""})),createdAt:new Date,modifiedAt:null,organizationId:"",isArchived:!1,trialInterval:null,trialIntervalCount:null,recurringInterval:e.recurringInterval??null,recurringIntervalCount:e.recurringIntervalCount??null,prices:e.prices.map(t=>{switch(t.amountType){case"free":return{id:crypto.randomUUID(),type:e.recurringInterval?"recurring":"one_time",recurringInterval:null,amountType:"free",isArchived:!1,productId:e.id,createdAt:new Date,modifiedAt:null,source:"catalog"};case"fixed":return{id:crypto.randomUUID(),type:e.recurringInterval?"recurring":"one_time",recurringInterval:null,amountType:t.amountType,priceAmount:t.priceAmount,priceCurrency:t.priceCurrency??"usd",isArchived:!1,productId:e.id,createdAt:new Date,modifiedAt:null,source:"catalog"};case"custom":return{id:crypto.randomUUID(),type:e.recurringInterval?"recurring":"one_time",recurringInterval:null,amountType:t.amountType,presetAmount:t.presetAmount??0,minimumAmount:t.minimumAmount??0,maximumAmount:t.maximumAmount??0,priceCurrency:t.priceCurrency??"usd",productId:e.id,isArchived:!1,createdAt:new Date,modifiedAt:null,source:"catalog"};case"metered_unit":return{id:crypto.randomUUID(),type:e.recurringInterval?"recurring":"one_time",recurringInterval:null,amountType:t.amountType,capAmount:t.capAmount??null,priceCurrency:t.priceCurrency??"usd",meter:{id:crypto.randomUUID(),name:"usage base"},meterId:crypto.randomUUID(),productId:e.id,unitAmount:"1",isArchived:!1,createdAt:new Date,modifiedAt:null,source:"catalog"};case"seat_based":return{id:crypto.randomUUID(),type:e.recurringInterval?"recurring":"one_time",recurringInterval:null,amountType:t.amountType,priceCurrency:t.priceCurrency??"usd",seatTiers:t.seatTiers,productId:e.id,createdAt:new Date,modifiedAt:null,source:"catalog",isArchived:!1}}})}}function y(e){let t=JSON.stringify(e,null,2).replace(/"(\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\d.\d\d\dZ)"/g,i=>`new Date(${i})`);return`export const ${f(e.name)}Product = ${t} as const satisfies Product`}async function A(e,t){if(typeof window<"u")throw new Error("writeProductsToFile is not supported in the browser");let i=`
|
|
2
|
+
import type { Product } from "@polar-sh/sdk/models/components/product.js"
|
|
3
|
+
|
|
4
|
+
${e.map(y).join(`
|
|
5
|
+
|
|
6
|
+
`)}
|
|
7
|
+
`,c=await import("fs/promises").then(n=>n.writeFile),s=await import("prettier").then(n=>n.default).catch(()=>null);if(s){let n=await import("path").then(a=>a.default),o=await import("fs").then(a=>a.existsSync),r=n.dirname(n.resolve(import.meta.url)),l=["prettier.config.js","prettier.config.ts"].map(a=>n.resolve(r,a)).find(a=>o(a)),P=l?await import(l).then(a=>a.default).catch(()=>null):null;i=await s.format(i.trim(),{parser:"typescript",...P})}await c(t,i)}import d from"picocolors";function T(e){let t=[];function i({key:s,...n}){n.prices=n.prices.map(r=>("priceAmount"in r&&(r.priceAmount=Math.round(r.priceAmount*100)),"presetAmount"in r&&typeof r.presetAmount=="number"&&(r.presetAmount=Math.round(r.presetAmount*100)),"minimumAmount"in r&&typeof r.minimumAmount=="number"&&(r.minimumAmount=Math.round(r.minimumAmount*100)),"maximumAmount"in r&&typeof r.maximumAmount=="number"&&(r.maximumAmount=Math.round(r.maximumAmount*100)),r));let o={...n,metadata:{key:s,...n.metadata}};return t.push(o),o}async function c(){globalThis.polarClient=e.client,globalThis.polarOrganizationId=e.organizationId;let s=await p(),n=[];for(let o of t){if(!o.metadata?.key)throw new Error(`Missing 'key' in product ${o.name}`);if(o.virtual){let u=g(o);n.push(u);continue}let r=s.products.find(u=>u.metadata.key===o.metadata?.key);if(r){let u=await polarClient.products.update({id:r.id,productUpdate:o});n.push(u),console.log(d.green(`Updated product ${d.bold(o.name)}`))}else{let u=await polarClient.products.create(o);n.push(u),console.log(d.green(`Created new product ${d.bold(o.name)}`))}}return globalThis.polarClient=void 0,globalThis.polarOrganizationId=void 0,{updatedProducts:n}}return{defineProduct:i,push:c}}export{T as createStoreFn,A as writeProductsToFile};
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils.ts","../src/lib.ts","../src/index.ts"],"sourcesContent":["import { Pagination } from \"@polar-sh/sdk/models/components/pagination.js\"\nimport { Product } from \"@polar-sh/sdk/models/components/product.js\"\nimport { ProductCreate } from \"@polar-sh/sdk/models/components/productcreate.js\"\nimport { PageIterator } from \"@polar-sh/sdk/types/operations.js\"\n\nexport async function fetchPolarIterator<T>(\n iterator: Promise<\n PageIterator<\n {\n result: {\n items: Array<T>\n pagination: Pagination\n }\n },\n { page: number }\n >\n >\n) {\n const items: T[] = []\n\n const result = await iterator\n\n for await (const page of result) {\n items.push(...page.result.items)\n }\n\n return items\n}\n\nexport function productMatchesProductCreate(product: Product, productCreate: ProductCreate) {\n return nonStrictDeepEqual(productCreate, product)\n}\n\nfunction nonStrictDeepEqual(a: any, b: any): boolean {\n if (a instanceof Date && typeof b === \"string\") {\n return a.toISOString() === b\n }\n if (b instanceof Date && typeof a === \"string\") {\n return b.toISOString() === a\n }\n\n if (typeof a !== typeof b) {\n return false\n }\n\n if (Array.isArray(a) !== Array.isArray(b)) {\n return false\n }\n\n if (Array.isArray(a)) {\n if (a.length !== b.length) {\n return false\n }\n\n for (let i = 0; i < a.length; i++) {\n if (!nonStrictDeepEqual(a[i], b[i])) {\n return false\n }\n }\n\n return true\n }\n\n if (typeof a === \"object\") {\n for (const key in a) {\n let aVal = a[key]\n\n // patch priceAmount\n if (key === \"priceAmount\") {\n aVal = Math.round(a[key] * 100)\n }\n\n if (!nonStrictDeepEqual(aVal, b[key])) {\n return false\n }\n }\n\n return true\n }\n\n if (a !== b) {\n return false\n }\n\n return true\n}\n\nexport function toCamelCase(str: string) {\n if (!str) return str\n\n return str\n .split(/[\\s-_]+/)\n .map((word, index) => {\n // Convert to lowercase first\n word = word.toLowerCase()\n // Capitalize all words except the first one\n if (index > 0) {\n return word.charAt(0).toUpperCase() + word.slice(1)\n }\n return word\n })\n .join(\"\")\n}\n","import { fetchPolarIterator, toCamelCase } from \"./utils\"\nimport { Product } from \"@polar-sh/sdk/models/components/product.js\"\nimport { ProductPriceSeatBased } from \"@polar-sh/sdk/models/components/productpriceseatbased.js\"\nimport { ProductPriceMeteredUnit } from \"@polar-sh/sdk/models/components/productpricemeteredunit.js\"\nimport { ProductPriceCustom } from \"@polar-sh/sdk/models/components/productpricecustom.js\"\nimport { ProductPriceFixed } from \"@polar-sh/sdk/models/components/productpricefixed.js\"\nimport { ProductPriceFree } from \"@polar-sh/sdk/models/components/productpricefree.js\"\nimport { SubscriptionRecurringInterval } from \"@polar-sh/sdk/models/components/subscriptionrecurringinterval.js\"\nimport { ProductCreateDefinition } from \"./types\"\n\nexport async function downloadStoreFromCloud() {\n const [products, benefits] = await Promise.all([\n fetchPolarIterator(\n globalThis.polarClient.products.list({\n organizationId: globalThis.polarOrganizationId,\n }),\n ),\n fetchPolarIterator(\n globalThis.polarClient.benefits.list({\n organizationId: globalThis.polarOrganizationId,\n }),\n ),\n ])\n\n return {\n products,\n benefits,\n }\n}\n\nexport function mapVirtualProduct(\n product: ProductCreateDefinition<any> & { virtual: true },\n): Product {\n return {\n ...product,\n description: product.description ?? \"\",\n metadata: product.metadata ?? {},\n isRecurring: !!product.recurringInterval,\n attachedCustomFields: [],\n benefits: [],\n medias: (product.medias ?? []).map((media) => ({\n id: crypto.randomUUID(),\n organizationId: \"\",\n name: \"\",\n path: media, // temp solution\n mimeType: \"\",\n size: 0,\n storageVersion: null,\n checksumEtag: null,\n checksumSha256Base64: null,\n checksumSha256Hex: null,\n version: null,\n service: \"product_media\",\n lastModifiedAt: null,\n isUploaded: false,\n createdAt: new Date(),\n sizeReadable: \"0 kb\",\n publicUrl: \"\",\n })),\n createdAt: new Date(),\n modifiedAt: null,\n organizationId: \"\",\n isArchived: false,\n trialInterval: null,\n trialIntervalCount: null,\n recurringInterval: (product.recurringInterval ?? null) as SubscriptionRecurringInterval | null,\n recurringIntervalCount: (product.recurringIntervalCount ?? null) as number | null,\n prices: product.prices.map((price) => {\n switch (price.amountType) {\n case \"free\":\n return {\n id: crypto.randomUUID(),\n type: product.recurringInterval ? \"recurring\" : \"one_time\",\n recurringInterval: null,\n amountType: \"free\",\n isArchived: false,\n productId: product.id,\n createdAt: new Date(),\n modifiedAt: null,\n source: \"catalog\",\n } satisfies ProductPriceFree\n case \"fixed\":\n return {\n id: crypto.randomUUID(),\n type: product.recurringInterval ? \"recurring\" : \"one_time\",\n recurringInterval: null,\n amountType: price.amountType,\n priceAmount: price.priceAmount,\n priceCurrency: price.priceCurrency ?? \"usd\",\n isArchived: false,\n productId: product.id,\n createdAt: new Date(),\n modifiedAt: null,\n source: \"catalog\",\n } satisfies ProductPriceFixed\n case \"custom\":\n return {\n id: crypto.randomUUID(),\n type: product.recurringInterval ? \"recurring\" : \"one_time\",\n recurringInterval: null,\n amountType: price.amountType,\n presetAmount: price.presetAmount ?? 0,\n minimumAmount: price.minimumAmount ?? 0,\n maximumAmount: price.maximumAmount ?? 0,\n priceCurrency: price.priceCurrency ?? \"usd\",\n productId: product.id,\n isArchived: false,\n createdAt: new Date(),\n modifiedAt: null,\n source: \"catalog\",\n } satisfies ProductPriceCustom\n case \"metered_unit\":\n return {\n id: crypto.randomUUID(),\n type: product.recurringInterval ? \"recurring\" : \"one_time\",\n recurringInterval: null,\n amountType: price.amountType,\n capAmount: price.capAmount ?? null,\n priceCurrency: price.priceCurrency ?? \"usd\",\n meter: {\n id: crypto.randomUUID(),\n name: \"usage base\",\n },\n meterId: crypto.randomUUID(),\n productId: product.id,\n unitAmount: \"1\",\n isArchived: false,\n createdAt: new Date(),\n modifiedAt: null,\n source: \"catalog\",\n } satisfies ProductPriceMeteredUnit\n case \"seat_based\":\n return {\n id: crypto.randomUUID(),\n type: product.recurringInterval ? \"recurring\" : \"one_time\",\n recurringInterval: null,\n amountType: price.amountType,\n priceCurrency: price.priceCurrency ?? \"usd\",\n seatTiers: price.seatTiers,\n productId: product.id,\n createdAt: new Date(),\n modifiedAt: null,\n source: \"catalog\",\n isArchived: false,\n } satisfies ProductPriceSeatBased\n }\n }),\n } satisfies Product\n}\n\nfunction formatProduct(product: Product) {\n const parsedJson = JSON.stringify(product, null, 2)\n // initialize timestmaps\n .replace(/\"(\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d.\\d\\d\\dZ)\"/g, (val) => `new Date(${val})`)\n return `export const ${toCamelCase(\n product.name,\n )}Product = ${parsedJson} as const satisfies Product`\n}\n\nexport async function writeProductsToFile(products: Product[], path: string) {\n if (typeof window !== \"undefined\") {\n throw new Error(\"writeProductsToFile is not supported in the browser\")\n }\n\n let storeProductsFileContent = `\nimport type { Product } from \"@polar-sh/sdk/models/components/product.js\"\n\n${products.map(formatProduct).join(\"\\n\\n\")}\n`\n\n const writeFile = await import(\"fs/promises\").then((m) => m.writeFile)\n const prettier = await import(\"prettier\").then((m) => m.default).catch(() => null)\n\n if (prettier) {\n const path = await import(\"path\").then((m) => m.default)\n const existsSync = await import(\"fs\").then((m) => m.existsSync)\n const basePath = path.dirname(path.resolve(import.meta.url))\n const prettierConfigs = [\"prettier.config.js\", \"prettier.config.ts\"].map((config) =>\n path.resolve(basePath, config),\n )\n const prettierConfigPath = prettierConfigs.find((config) => existsSync(config))\n const prettierConfig = prettierConfigPath\n ? await import(prettierConfigPath).then((m) => m.default).catch(() => null)\n : null\n\n storeProductsFileContent = await prettier.format(storeProductsFileContent.trim(), {\n parser: \"typescript\",\n ...prettierConfig,\n })\n }\n\n await writeFile(path, storeProductsFileContent)\n}\n","import { Polar } from \"@polar-sh/sdk\"\nimport { ProductCreate } from \"@polar-sh/sdk/models/components/productcreate.js\"\nimport { ProductCreateDefinition, ProductDefinition } from \"./types\"\nimport { downloadStoreFromCloud, mapVirtualProduct, writeProductsToFile } from \"./lib\"\nimport { Product } from \"@polar-sh/sdk/models/components/product.js\"\nimport pc from \"picocolors\"\n\ninterface CreateStoreFnOptions {\n client: Polar\n organizationId: string\n}\n\nfunction createStoreFn(options: CreateStoreFnOptions) {\n let definedProducts: ProductCreateDefinition<any>[] = []\n\n function defineProduct<TMeta extends ProductCreate[\"metadata\"]>({\n key,\n ...details\n }: ProductDefinition<TMeta>) {\n details.prices = details.prices.map((price) => {\n if (\"priceAmount\" in price) {\n price.priceAmount = Math.round(price.priceAmount * 100)\n }\n if (\"presetAmount\" in price && typeof price.presetAmount === \"number\") {\n price.presetAmount = Math.round(price.presetAmount * 100)\n }\n if (\"minimumAmount\" in price && typeof price.minimumAmount === \"number\") {\n price.minimumAmount = Math.round(price.minimumAmount * 100)\n }\n if (\"maximumAmount\" in price && typeof price.maximumAmount === \"number\") {\n price.maximumAmount = Math.round(price.maximumAmount * 100)\n }\n return price\n })\n\n const definition = {\n ...details,\n metadata: {\n key,\n ...details.metadata,\n } as TMeta & { key: string },\n } satisfies ProductCreateDefinition<TMeta>\n\n definedProducts.push(definition)\n\n return definition\n }\n\n async function push() {\n globalThis.polarClient = options.client\n globalThis.polarOrganizationId = options.organizationId\n\n const polarStore = await downloadStoreFromCloud()\n\n let updatedProducts: Product[] = []\n\n for (const product of definedProducts) {\n if (!product.metadata?.key) {\n throw new Error(`Missing 'key' in product ${product.name}`)\n }\n\n if (product.virtual) {\n const virtualProduct = mapVirtualProduct(product)\n updatedProducts.push(virtualProduct)\n continue\n }\n\n const existingProduct = polarStore.products.find(\n (p) => p.metadata.key === product.metadata?.key,\n )\n\n if (existingProduct) {\n const updatedProduct = await polarClient.products.update({\n id: existingProduct.id,\n productUpdate: product,\n })\n updatedProducts.push(updatedProduct)\n\n console.log(pc.green(`Updated product ${pc.bold(product.name)}`))\n } else {\n const createdProduct = await polarClient.products.create(product)\n updatedProducts.push(createdProduct)\n\n console.log(pc.green(`Created new product ${pc.bold(product.name)}`))\n }\n }\n\n // @ts-expect-error\n globalThis.polarClient = undefined\n // @ts-expect-error\n globalThis.polarOrganizationId = undefined\n\n return {\n updatedProducts,\n }\n }\n\n return { defineProduct, push }\n}\n\nexport { createStoreFn, writeProductsToFile }\n"],"mappings":"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","names":["fetchPolarIterator","iterator","items","result","page","toCamelCase","str","word","index","downloadStoreFromCloud","products","benefits","fetchPolarIterator","mapVirtualProduct","product","media","price","formatProduct","parsedJson","val","toCamelCase","writeProductsToFile","path","storeProductsFileContent","writeFile","m","prettier","existsSync","basePath","prettierConfigPath","config","prettierConfig","pc","createStoreFn","options","definedProducts","defineProduct","key","details","price","definition","push","polarStore","downloadStoreFromCloud","updatedProducts","product","virtualProduct","mapVirtualProduct","existingProduct","p","updatedProduct","createdProduct"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "store-fn",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A simple utility function to sync your Polar store with code.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"typesafe",
|
|
9
|
+
"typescript",
|
|
10
|
+
"routes",
|
|
11
|
+
"urls"
|
|
12
|
+
],
|
|
13
|
+
"author": "Mattia Dalzocchio <mattiadalzocchio@me.com>",
|
|
14
|
+
"homepage": "https://github.com/mattiaz9/store-fn#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/mattiaz9/store-fn.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/mattiaz9/store-fn/issues"
|
|
21
|
+
},
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"bin": {
|
|
26
|
+
"store-fn": "./dist/cli.js"
|
|
27
|
+
},
|
|
28
|
+
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
|
29
|
+
"scripts": {
|
|
30
|
+
"dev": "rm -rf dist && tsup --watch --config tsup.config.ts",
|
|
31
|
+
"build": "tsup --minify --config tsup.config.ts",
|
|
32
|
+
"test": "vitest",
|
|
33
|
+
"perf": "tsc --noEmit ./tests/perf.test.ts",
|
|
34
|
+
"lint": "eslint --ext .ts,.tsx src",
|
|
35
|
+
"cli:test": "tsx src/cli.ts push -i test-store.ts -o test-products.ts"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@polar-sh/sdk": "^0.40.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@polar-sh/sdk": "0.42.1",
|
|
42
|
+
"picocolors": "1.1.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "25.0.3",
|
|
46
|
+
"prettier": "3.7.4",
|
|
47
|
+
"tsup": "8.5.1",
|
|
48
|
+
"tsx": "4.21.0",
|
|
49
|
+
"typescript": "5.9.3",
|
|
50
|
+
"vite": "7.3.0",
|
|
51
|
+
"vitest": "4.0.16"
|
|
52
|
+
}
|
|
53
|
+
}
|