@porulle/import-shopify 0.64.0 → 0.65.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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -15
- package/package.json +4 -4
- package/src/index.ts +56 -14
package/dist/index.d.ts
CHANGED
|
@@ -134,6 +134,7 @@ export interface ShopifyImportOptions {
|
|
|
134
134
|
apiVersion?: string;
|
|
135
135
|
products?: ShopifyProduct[];
|
|
136
136
|
customers?: ShopifyCustomer[];
|
|
137
|
+
currency?: string;
|
|
137
138
|
fetchImpl?: typeof fetch;
|
|
138
139
|
mediaFetcher?: (url: string) => Promise<{
|
|
139
140
|
data: ArrayBuffer;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAEnE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,oBAAoB,EAAE,CAAC;IACjC,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,CAAC,KAAK,EAAE;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACpD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5B,gBAAgB,CAAC,CAAC,KAAK,EAAE;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5B,iBAAiB,CAAC,CAAC,KAAK,EAAE;QACxB,YAAY,EAAE,MAAM,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5B,aAAa,CAAC,CAAC,KAAK,EAAE;QACpB,QAAQ,EAAE,MAAM,CAAC;QACjB,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5B,WAAW,CAAC,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,WAAW,CAAC;QAClB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzC,WAAW,CAAC,CAAC,KAAK,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,IAAI,EAAE,SAAS,GAAG,SAAS,CAAC;KAC7B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,cAAc,CAAC,CAAC,KAAK,EAAE;QACrB,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,KAAK,CAAC;YAChB,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;YAC7B,SAAS,EAAE,OAAO,CAAC;YACnB,SAAS,EAAE,MAAM,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC;YACjB,KAAK,EAAE,MAAM,CAAC;YACd,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,OAAO,EAAE,MAAM,CAAC;YAChB,KAAK,CAAC,EAAE,MAAM,CAAC;SAChB,CAAC,CAAC;QACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,mBAAmB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxF;AAoLD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAoL/G"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Err, Ok } from "@porulle/core";
|
|
1
|
+
import { Err, Ok, toMinorUnits } from "@porulle/core";
|
|
2
2
|
function slugify(value) {
|
|
3
3
|
return value
|
|
4
4
|
.toLowerCase()
|
|
@@ -6,13 +6,10 @@ function slugify(value) {
|
|
|
6
6
|
.replace(/[^a-z0-9]+/g, "-")
|
|
7
7
|
.replace(/^-+|-+$/g, "") || `product-${crypto.randomUUID().slice(0, 8)}`;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
12
|
-
|
|
13
|
-
if (!Number.isFinite(parsed))
|
|
14
|
-
return 0;
|
|
15
|
-
return Math.round(parsed * 100);
|
|
9
|
+
function normalizeCurrency(value) {
|
|
10
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
11
|
+
return undefined;
|
|
12
|
+
return value.trim().toUpperCase();
|
|
16
13
|
}
|
|
17
14
|
async function fetchJson(fetchImpl, url, apiKey) {
|
|
18
15
|
try {
|
|
@@ -28,7 +25,7 @@ async function fetchJson(fetchImpl, url, apiKey) {
|
|
|
28
25
|
message: `Shopify API request failed (${response.status}) for ${url}.`,
|
|
29
26
|
});
|
|
30
27
|
}
|
|
31
|
-
return Ok((await response.json()));
|
|
28
|
+
return Ok({ data: (await response.json()), response });
|
|
32
29
|
}
|
|
33
30
|
catch (error) {
|
|
34
31
|
return Err({
|
|
@@ -37,6 +34,22 @@ async function fetchJson(fetchImpl, url, apiKey) {
|
|
|
37
34
|
});
|
|
38
35
|
}
|
|
39
36
|
}
|
|
37
|
+
function nextPageUrl(response) {
|
|
38
|
+
const link = response.headers.get("link") ?? "";
|
|
39
|
+
return link.match(/<([^>]+)>;\s*rel=(?:"next"|next)(?:\s*(?:,|$))/)?.[1] ?? null;
|
|
40
|
+
}
|
|
41
|
+
async function loadShopifyPages(fetchImpl, initialUrl, apiKey, readItems) {
|
|
42
|
+
const items = [];
|
|
43
|
+
let url = initialUrl;
|
|
44
|
+
while (url) {
|
|
45
|
+
const response = await fetchJson(fetchImpl, url, apiKey);
|
|
46
|
+
if (!response.ok)
|
|
47
|
+
return response;
|
|
48
|
+
items.push(...readItems(response.value.data));
|
|
49
|
+
url = nextPageUrl(response.value.response);
|
|
50
|
+
}
|
|
51
|
+
return Ok(items);
|
|
52
|
+
}
|
|
40
53
|
function filenameFromUrl(url) {
|
|
41
54
|
try {
|
|
42
55
|
const parsed = new URL(url);
|
|
@@ -70,10 +83,10 @@ async function loadProducts(options) {
|
|
|
70
83
|
const base = options.storeUrl.replace(/\/$/, "");
|
|
71
84
|
const version = options.apiVersion ?? "2024-10";
|
|
72
85
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
73
|
-
const response = await
|
|
86
|
+
const response = await loadShopifyPages(fetchImpl, `${base}/admin/api/${version}/products.json?limit=250`, options.apiKey, (data) => data.products ?? []);
|
|
74
87
|
if (!response.ok)
|
|
75
88
|
return response;
|
|
76
|
-
return Ok(response.value
|
|
89
|
+
return Ok(response.value);
|
|
77
90
|
}
|
|
78
91
|
async function loadCustomers(options) {
|
|
79
92
|
if (options.customers)
|
|
@@ -84,10 +97,21 @@ async function loadCustomers(options) {
|
|
|
84
97
|
const base = options.storeUrl.replace(/\/$/, "");
|
|
85
98
|
const version = options.apiVersion ?? "2024-10";
|
|
86
99
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
87
|
-
const response = await
|
|
100
|
+
const response = await loadShopifyPages(fetchImpl, `${base}/admin/api/${version}/customers.json?limit=250`, options.apiKey, (data) => data.customers ?? []);
|
|
88
101
|
if (!response.ok)
|
|
89
102
|
return response;
|
|
90
|
-
return Ok(response.value
|
|
103
|
+
return Ok(response.value);
|
|
104
|
+
}
|
|
105
|
+
async function loadCurrency(options) {
|
|
106
|
+
const configured = normalizeCurrency(options.currency);
|
|
107
|
+
if (configured)
|
|
108
|
+
return configured;
|
|
109
|
+
if (!options.storeUrl || !options.apiKey)
|
|
110
|
+
return undefined;
|
|
111
|
+
const base = options.storeUrl.replace(/\/$/, "");
|
|
112
|
+
const version = options.apiVersion ?? "2024-10";
|
|
113
|
+
const response = await fetchJson(options.fetchImpl ?? fetch, `${base}/admin/api/${version}/shop.json`, options.apiKey);
|
|
114
|
+
return response.ok ? normalizeCurrency(response.value.data.shop?.currency) : undefined;
|
|
91
115
|
}
|
|
92
116
|
function toAddress(address) {
|
|
93
117
|
return {
|
|
@@ -111,6 +135,7 @@ export async function importShopifyCatalog(options) {
|
|
|
111
135
|
const customersResult = await loadCustomers(options);
|
|
112
136
|
if (!customersResult.ok)
|
|
113
137
|
return customersResult;
|
|
138
|
+
const currency = await loadCurrency(options);
|
|
114
139
|
const summary = {
|
|
115
140
|
entitiesImported: 0,
|
|
116
141
|
variantsImported: 0,
|
|
@@ -167,6 +192,9 @@ export async function importShopifyCatalog(options) {
|
|
|
167
192
|
if (options.target.createVariant && (product.variants?.length ?? 0) > 0) {
|
|
168
193
|
for (const variant of product.variants ?? []) {
|
|
169
194
|
try {
|
|
195
|
+
// An unknown shop currency falls back to two decimals, as before.
|
|
196
|
+
const price = toMinorUnits(variant.price, currency ?? "");
|
|
197
|
+
const compareAtPrice = toMinorUnits(variant.compare_at_price, currency ?? "");
|
|
170
198
|
const optionValues = [];
|
|
171
199
|
const selectors = [variant.option1, variant.option2, variant.option3];
|
|
172
200
|
const productOptions = product.options ?? [];
|
|
@@ -191,8 +219,8 @@ export async function importShopifyCatalog(options) {
|
|
|
191
219
|
source: "shopify",
|
|
192
220
|
shopifyVariantId: variant.id,
|
|
193
221
|
title: variant.title,
|
|
194
|
-
price:
|
|
195
|
-
compareAtPrice:
|
|
222
|
+
...(price !== undefined ? { price } : {}),
|
|
223
|
+
...(compareAtPrice !== undefined ? { compareAtPrice } : {}),
|
|
196
224
|
},
|
|
197
225
|
});
|
|
198
226
|
summary.variantsImported += 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@porulle/import-shopify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@porulle/core": "0.
|
|
14
|
+
"@porulle/core": "0.65.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^24.5.2",
|
|
18
18
|
"eslint": "^9.39.1",
|
|
19
19
|
"typescript": "5.9.2",
|
|
20
20
|
"vitest": "^3.2.4",
|
|
21
|
-
"@porulle/
|
|
22
|
-
"@porulle/
|
|
21
|
+
"@porulle/typescript-config": "0.1.0",
|
|
22
|
+
"@porulle/eslint-config": "0.1.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Err, Ok, type Result } from "@porulle/core";
|
|
1
|
+
import { Err, Ok, toMinorUnits, type Result } from "@porulle/core";
|
|
2
2
|
|
|
3
3
|
export interface ShopifyImage {
|
|
4
4
|
id: number;
|
|
@@ -128,6 +128,7 @@ export interface ShopifyImportOptions {
|
|
|
128
128
|
apiVersion?: string;
|
|
129
129
|
products?: ShopifyProduct[];
|
|
130
130
|
customers?: ShopifyCustomer[];
|
|
131
|
+
currency?: string;
|
|
131
132
|
fetchImpl?: typeof fetch;
|
|
132
133
|
mediaFetcher?: (url: string) => Promise<{ data: ArrayBuffer; contentType: string; filename?: string }>;
|
|
133
134
|
entityType?: string;
|
|
@@ -149,18 +150,16 @@ function slugify(value: string): string {
|
|
|
149
150
|
.replace(/^-+|-+$/g, "") || `product-${crypto.randomUUID().slice(0, 8)}`;
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
function
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
if (!Number.isFinite(parsed)) return 0;
|
|
156
|
-
return Math.round(parsed * 100);
|
|
153
|
+
function normalizeCurrency(value: unknown): string | undefined {
|
|
154
|
+
if (typeof value !== "string" || value.trim() === "") return undefined;
|
|
155
|
+
return value.trim().toUpperCase();
|
|
157
156
|
}
|
|
158
157
|
|
|
159
158
|
async function fetchJson<T>(
|
|
160
159
|
fetchImpl: typeof fetch,
|
|
161
160
|
url: string,
|
|
162
161
|
apiKey: string,
|
|
163
|
-
): Promise<Result<T>> {
|
|
162
|
+
): Promise<Result<{ data: T; response: Response }>> {
|
|
164
163
|
try {
|
|
165
164
|
const response = await fetchImpl(url, {
|
|
166
165
|
headers: {
|
|
@@ -176,7 +175,7 @@ async function fetchJson<T>(
|
|
|
176
175
|
});
|
|
177
176
|
}
|
|
178
177
|
|
|
179
|
-
return Ok((await response.json()) as T);
|
|
178
|
+
return Ok({ data: (await response.json()) as T, response });
|
|
180
179
|
} catch (error) {
|
|
181
180
|
return Err({
|
|
182
181
|
code: "SHOPIFY_API_FAILED",
|
|
@@ -185,6 +184,28 @@ async function fetchJson<T>(
|
|
|
185
184
|
}
|
|
186
185
|
}
|
|
187
186
|
|
|
187
|
+
function nextPageUrl(response: Response): string | null {
|
|
188
|
+
const link = response.headers.get("link") ?? "";
|
|
189
|
+
return link.match(/<([^>]+)>;\s*rel=(?:"next"|next)(?:\s*(?:,|$))/)?.[1] ?? null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function loadShopifyPages<T, TItem>(
|
|
193
|
+
fetchImpl: typeof fetch,
|
|
194
|
+
initialUrl: string,
|
|
195
|
+
apiKey: string,
|
|
196
|
+
readItems: (data: T) => TItem[],
|
|
197
|
+
): Promise<Result<TItem[]>> {
|
|
198
|
+
const items: TItem[] = [];
|
|
199
|
+
let url: string | null = initialUrl;
|
|
200
|
+
while (url) {
|
|
201
|
+
const response = await fetchJson<T>(fetchImpl, url, apiKey);
|
|
202
|
+
if (!response.ok) return response;
|
|
203
|
+
items.push(...readItems(response.value.data));
|
|
204
|
+
url = nextPageUrl(response.value.response);
|
|
205
|
+
}
|
|
206
|
+
return Ok(items);
|
|
207
|
+
}
|
|
208
|
+
|
|
188
209
|
function filenameFromUrl(url: string): string {
|
|
189
210
|
try {
|
|
190
211
|
const parsed = new URL(url);
|
|
@@ -224,14 +245,15 @@ async function loadProducts(options: ShopifyImportOptions): Promise<Result<Shopi
|
|
|
224
245
|
const base = options.storeUrl.replace(/\/$/, "");
|
|
225
246
|
const version = options.apiVersion ?? "2024-10";
|
|
226
247
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
227
|
-
const response = await
|
|
248
|
+
const response = await loadShopifyPages<{ products?: ShopifyProduct[] }, ShopifyProduct>(
|
|
228
249
|
fetchImpl,
|
|
229
250
|
`${base}/admin/api/${version}/products.json?limit=250`,
|
|
230
251
|
options.apiKey,
|
|
252
|
+
(data) => data.products ?? [],
|
|
231
253
|
);
|
|
232
254
|
|
|
233
255
|
if (!response.ok) return response;
|
|
234
|
-
return Ok(response.value
|
|
256
|
+
return Ok(response.value);
|
|
235
257
|
}
|
|
236
258
|
|
|
237
259
|
async function loadCustomers(options: ShopifyImportOptions): Promise<Result<ShopifyCustomer[]>> {
|
|
@@ -244,14 +266,30 @@ async function loadCustomers(options: ShopifyImportOptions): Promise<Result<Shop
|
|
|
244
266
|
const base = options.storeUrl.replace(/\/$/, "");
|
|
245
267
|
const version = options.apiVersion ?? "2024-10";
|
|
246
268
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
247
|
-
const response = await
|
|
269
|
+
const response = await loadShopifyPages<{ customers?: ShopifyCustomer[] }, ShopifyCustomer>(
|
|
248
270
|
fetchImpl,
|
|
249
271
|
`${base}/admin/api/${version}/customers.json?limit=250`,
|
|
250
272
|
options.apiKey,
|
|
273
|
+
(data) => data.customers ?? [],
|
|
251
274
|
);
|
|
252
275
|
|
|
253
276
|
if (!response.ok) return response;
|
|
254
|
-
return Ok(response.value
|
|
277
|
+
return Ok(response.value);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function loadCurrency(options: ShopifyImportOptions): Promise<string | undefined> {
|
|
281
|
+
const configured = normalizeCurrency(options.currency);
|
|
282
|
+
if (configured) return configured;
|
|
283
|
+
if (!options.storeUrl || !options.apiKey) return undefined;
|
|
284
|
+
|
|
285
|
+
const base = options.storeUrl.replace(/\/$/, "");
|
|
286
|
+
const version = options.apiVersion ?? "2024-10";
|
|
287
|
+
const response = await fetchJson<{ shop?: { currency?: unknown } }>(
|
|
288
|
+
options.fetchImpl ?? fetch,
|
|
289
|
+
`${base}/admin/api/${version}/shop.json`,
|
|
290
|
+
options.apiKey,
|
|
291
|
+
);
|
|
292
|
+
return response.ok ? normalizeCurrency(response.value.data.shop?.currency) : undefined;
|
|
255
293
|
}
|
|
256
294
|
|
|
257
295
|
function toAddress(address: ShopifyAddress): {
|
|
@@ -288,6 +326,7 @@ export async function importShopifyCatalog(options: ShopifyImportOptions): Promi
|
|
|
288
326
|
|
|
289
327
|
const customersResult = await loadCustomers(options);
|
|
290
328
|
if (!customersResult.ok) return customersResult;
|
|
329
|
+
const currency = await loadCurrency(options);
|
|
291
330
|
|
|
292
331
|
const summary: ShopifyImportSummary = {
|
|
293
332
|
entitiesImported: 0,
|
|
@@ -352,6 +391,9 @@ export async function importShopifyCatalog(options: ShopifyImportOptions): Promi
|
|
|
352
391
|
if (options.target.createVariant && (product.variants?.length ?? 0) > 0) {
|
|
353
392
|
for (const variant of product.variants ?? []) {
|
|
354
393
|
try {
|
|
394
|
+
// An unknown shop currency falls back to two decimals, as before.
|
|
395
|
+
const price = toMinorUnits(variant.price, currency ?? "");
|
|
396
|
+
const compareAtPrice = toMinorUnits(variant.compare_at_price, currency ?? "");
|
|
355
397
|
const optionValues: string[] = [];
|
|
356
398
|
const selectors = [variant.option1, variant.option2, variant.option3];
|
|
357
399
|
const productOptions = product.options ?? [];
|
|
@@ -376,8 +418,8 @@ export async function importShopifyCatalog(options: ShopifyImportOptions): Promi
|
|
|
376
418
|
source: "shopify",
|
|
377
419
|
shopifyVariantId: variant.id,
|
|
378
420
|
title: variant.title,
|
|
379
|
-
price:
|
|
380
|
-
compareAtPrice:
|
|
421
|
+
...(price !== undefined ? { price } : {}),
|
|
422
|
+
...(compareAtPrice !== undefined ? { compareAtPrice } : {}),
|
|
381
423
|
},
|
|
382
424
|
});
|
|
383
425
|
|