@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 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;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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 parseMoney(value) {
10
- if (!value)
11
- return 0;
12
- const parsed = Number.parseFloat(value);
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 fetchJson(fetchImpl, `${base}/admin/api/${version}/products.json?limit=250`, options.apiKey);
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.products ?? []);
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 fetchJson(fetchImpl, `${base}/admin/api/${version}/customers.json?limit=250`, options.apiKey);
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.customers ?? []);
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: parseMoney(variant.price),
195
- compareAtPrice: parseMoney(variant.compare_at_price),
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.64.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.64.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/eslint-config": "0.1.0",
22
- "@porulle/typescript-config": "0.1.0"
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 parseMoney(value: string | undefined | null): number {
153
- if (!value) return 0;
154
- const parsed = Number.parseFloat(value);
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 fetchJson<{ products: ShopifyProduct[] }>(
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.products ?? []);
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 fetchJson<{ customers: ShopifyCustomer[] }>(
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.customers ?? []);
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: parseMoney(variant.price),
380
- compareAtPrice: parseMoney(variant.compare_at_price),
421
+ ...(price !== undefined ? { price } : {}),
422
+ ...(compareAtPrice !== undefined ? { compareAtPrice } : {}),
381
423
  },
382
424
  });
383
425