Skip to content

Commit fc8ef6d

Browse files
committed
add discount support
1 parent 6722b3d commit fc8ef6d

File tree

6 files changed

+127
-32
lines changed

6 files changed

+127
-32
lines changed

package.json

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "polar-migrate",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"license": "Apache-2.0",
55
"bin": "bin/cli.js",
66
"type": "module",
@@ -13,14 +13,11 @@
1313
"test": "prettier --check . && xo && ava",
1414
"check": "biome check --write ./src"
1515
},
16-
"files": [
17-
"dist",
18-
"bin"
19-
],
16+
"files": ["dist", "bin"],
2017
"dependencies": {
2118
"@inkjs/ui": "^2.0.0",
2219
"@lemonsqueezy/lemonsqueezy.js": "^4.0.0",
23-
"@polar-sh/sdk": "^0.16.0",
20+
"@polar-sh/sdk": "^0.16.1",
2421
"@types/cross-spawn": "^6.0.6",
2522
"cross-spawn": "^7.0.3",
2623
"ink": "^4.4.1",
@@ -59,9 +56,7 @@
5956
"ts": "module",
6057
"tsx": "module"
6158
},
62-
"nodeArguments": [
63-
"--loader=ts-node/esm"
64-
]
59+
"nodeArguments": ["--loader=ts-node/esm"]
6560
},
6661
"xo": {
6762
"extends": "xo-react",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cli.tsx

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import { listDiscounts } from "@lemonsqueezy/lemonsqueezy.js";
12
import { Polar } from "@polar-sh/sdk";
3+
import type { Discount } from "@polar-sh/sdk/models/components/index.js";
4+
import type { Product } from "@polar-sh/sdk/models/components/product.js";
25
import meow from "meow";
36
import open from "open";
47
import { createLemonClient } from "./lemon.js";
@@ -83,20 +86,97 @@ meow(
8386

8487
const createdProducts = await Promise.all(
8588
variants.map((variant) => {
86-
const product = products.data?.data?.find(
89+
const lemonProduct = products.data?.data?.find(
8790
(product) => product.id === variant.attributes.product_id.toString(),
8891
);
8992

90-
if (!product) {
93+
if (!lemonProduct) {
9194
console.error(`Product not found for variant ${variant.id}`);
9295
process.exit(1);
9396
}
9497

95-
return createProduct(polar, organization, variant, product);
98+
return createProduct(polar, organization, variant, lemonProduct);
9699
}),
97100
);
98101

99-
await successMessage(organization, createdProducts, server);
102+
const variantWithProductMap = new Map<string, Product>();
103+
104+
for (const product of createdProducts) {
105+
variantWithProductMap.set(product.variantId, product.product);
106+
}
107+
108+
const discounts = await listDiscounts({
109+
filter: {
110+
storeId: store.id,
111+
},
112+
include: ["variants"],
113+
});
114+
115+
const publishedDiscounts =
116+
discounts.data?.data?.filter(
117+
(discount) => discount.attributes.status === "published",
118+
) ?? [];
119+
120+
let createdDiscounts: Discount[] = [];
121+
122+
try {
123+
createdDiscounts = await Promise.all(
124+
publishedDiscounts.map((discount) => {
125+
const commonProps = {
126+
code: discount.attributes.code,
127+
duration: discount.attributes.duration,
128+
durationInMonths: discount.attributes.duration_in_months,
129+
name: discount.attributes.name,
130+
maxRedemptions: discount.attributes.is_limited_redemptions
131+
? Math.max(discount.attributes.max_redemptions, 1)
132+
: undefined,
133+
startsAt: discount.attributes.starts_at
134+
? new Date(discount.attributes.starts_at)
135+
: undefined,
136+
endsAt: discount.attributes.expires_at
137+
? new Date(discount.attributes.expires_at)
138+
: undefined,
139+
organizationId: organization.id,
140+
};
141+
142+
const productsToAssociateWithDiscount =
143+
discount.relationships.variants.data
144+
?.map((variant) => variantWithProductMap.get(variant.id)?.id)
145+
.filter((id): id is string => id !== undefined) ?? [];
146+
147+
if (discount.attributes.amount_type === "fixed") {
148+
return polar.discounts.create({
149+
...commonProps,
150+
amount: discount.attributes.amount,
151+
type: "fixed",
152+
products:
153+
productsToAssociateWithDiscount?.length > 0
154+
? productsToAssociateWithDiscount
155+
: undefined,
156+
});
157+
}
158+
159+
return polar.discounts.create({
160+
...commonProps,
161+
basisPoints: discount.attributes.amount * 100,
162+
type: "percentage",
163+
products:
164+
productsToAssociateWithDiscount?.length > 0
165+
? productsToAssociateWithDiscount
166+
: undefined,
167+
});
168+
}),
169+
);
170+
} catch (e) {
171+
console.error(e);
172+
}
173+
174+
await successMessage(
175+
organization,
176+
createdProducts.map((p) => p.product),
177+
createdDiscounts,
178+
server,
179+
);
100180

101181
open(
102182
`https://${server === "sandbox" ? "sandbox." : ""}polar.sh/dashboard/${

src/product.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import fs from "node:fs";
2+
import https from "node:https";
3+
import os from "node:os";
4+
import path from "node:path";
15
import {
2-
listFiles,
36
type ListProducts,
47
type ListVariants,
8+
listFiles,
59
} from "@lemonsqueezy/lemonsqueezy.js";
610
import type { Polar } from "@polar-sh/sdk";
711
import type {
@@ -19,13 +23,9 @@ import type {
1923
ProductRecurringCreate,
2024
Timeframe,
2125
} from "@polar-sh/sdk/models/components";
22-
import fs from "node:fs";
23-
import path from "node:path";
24-
import os from "node:os";
2526
import mime from "mime-types";
26-
import https from "node:https";
27-
import { Upload } from "./upload.js";
2827
import { uploadFailedMessage, uploadMessage } from "./ui/upload.js";
28+
import { Upload } from "./upload.js";
2929

3030
const resolveInterval = (
3131
interval: ListVariants["data"][number]["attributes"]["interval"],
@@ -187,7 +187,10 @@ export const createProduct = async (
187187
await uploadFailedMessage();
188188
}
189189

190-
return product;
190+
return {
191+
variantId: variant.id,
192+
product,
193+
};
191194
};
192195

193196
const handleFiles = async (

src/ui/success.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { StatusMessage } from "@inkjs/ui";
2+
import type { Discount } from "@polar-sh/sdk/models/components/index.js";
23
import type { Organization } from "@polar-sh/sdk/models/components/organization.js";
34
import type { Product } from "@polar-sh/sdk/models/components/product.js";
45
import { Box, Text, render } from "ink";
@@ -7,6 +8,7 @@ import React from "react";
78
export const successMessage = async (
89
organization: Organization,
910
products: Product[],
11+
createdDiscounts: Discount[],
1012
server: "sandbox" | "production",
1113
) => {
1214
const { unmount, waitUntilExit } = render(
@@ -21,11 +23,26 @@ export const successMessage = async (
2123
<Text>
2224
Organization: <Text color="blue">{organization.name}</Text>
2325
</Text>
24-
<Text color="green">{products.length} Products Created:</Text>
25-
26-
{products.map((product) => (
27-
<Text key={product.id}>- {product.name}</Text>
28-
))}
26+
{products.length > 0 && (
27+
<>
28+
<Text color="green">{products.length} Products Created:</Text>
29+
{products.map((product) => (
30+
<Text key={product.id}>- {product.name}</Text>
31+
))}
32+
</>
33+
)}
34+
{createdDiscounts.length > 0 && (
35+
<>
36+
<Text color="green">
37+
{createdDiscounts.length} Discounts Created:
38+
</Text>
39+
{createdDiscounts.map((discount) => (
40+
<Text key={discount.id}>
41+
- {discount.name} ({discount.code})
42+
</Text>
43+
))}
44+
</>
45+
)}
2946
</Box>
3047
</Box>,
3148
);

src/upload.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import type { ReadStream } from "node:fs";
22
import type { Polar } from "@polar-sh/sdk";
33
import type {
4+
FileCreate,
45
FileRead,
56
FileUpload,
67
Organization,
78
S3FileCreatePart,
89
S3FileUploadCompletedPart,
910
S3FileUploadPart,
1011
} from "@polar-sh/sdk/models/components";
11-
import type { FilesCreateFileCreate } from "@polar-sh/sdk/models/operations";
1212

1313
const CHUNK_SIZE = 10000000; // 10MB
1414

@@ -74,7 +74,7 @@ export class Upload {
7474
const parts = await this.getMultiparts();
7575
const mimeType = this.file.type ?? "application/octet-stream";
7676

77-
const params: FilesCreateFileCreate = {
77+
const params: FileCreate = {
7878
organizationId: this.organization.id,
7979
service: "downloadable",
8080
name: this.file.name,

0 commit comments

Comments
 (0)