Setting the file. One moment.
Shopify Regional · Rp Source Shopify · wix/skills · Skills Docs
ContentsBack to the top of the page
14 of 51 lib/ shopify-regional.js
JavaScript · 126 lines · 6 KB
(
'node:fs/promises'
);
14 const path = require ( 'node:path' );
15 const { buildSourceRegional , writeAdapterSourceRegional } = require ( '../../../lib/source-regional.js' );
16 const { ShopifyCliError } = require ( './shopify-cli.js' );
17 const { REGIONAL_READS , at } = require ( './discovery-reads.js' );
18
19 const [ LOCALES_READ , COUNTRY_READ ] = REGIONAL_READS ;
20
21 async function readCaptured ( rawDir , entity ) {
22 try {
23 return JSON . parse ( await fs. readFile (path. join (rawDir, 'entities' , `${ entity }.json` ), 'utf8' ));
24 } catch (error) {
25 if (error.code === 'ENOENT' ) return null ;
26 throw error;
27 }
28 }
29
30 // A throttled read is a failed read of that value (unknown), never a stopped discovery.
31 async function readQuery ( execute , query ) {
32 try {
33 return await execute (query);
34 } catch (error) {
35 if (error instanceof ShopifyCliError && error.code === 'shopify_throttled' ) {
36 return { data: null , errors: [{ message: 'throttled' , extensions: { code: 'shopify_throttled' } }] };
37 }
38 throw error;
39 }
40 }
41
42 // Why a read gave no value: Shopify refusing the scope is `auth-gated`; any other error is `unreadable`.
43 function reasonFor ( errors ) {
44 const gated = (errors || []). some (( error ) => {
45 const code = error && ((error.extensions && error.extensions.code) || error.code);
46 return code === 'ACCESS_DENIED' || /access denied | access scope | required access/ i . test ( String ((error && error.message) || '' ));
47 });
48 return gated ? 'auth-gated' : 'unreadable' ;
49 }
50
51 function unknownFrom ( errors , evidence ) {
52 return { reason: reasonFor (errors), evidence };
53 }
54
55 // `he` -> language only; `pt-BR` -> language plus the locale tag. The language is handed over as
56 // Shopify wrote it: the library, not this code, decides whether it is a valid two-letter code.
57 function splitLocale ( locale ) {
58 const text = typeof locale === 'string' ? locale : '' ;
59 const [ language , region ] = text. split ( '-' );
60 return { language, locale: region ? text : undefined };
61 }
62
63 function localeFields ( result ) {
64 const evidence = 'shopLocales[primary]' ;
65 if (result.errors && result.errors. length ) {
66 const gated = unknownFrom (result.errors, evidence);
67 return { primaryLanguage: gated };
68 }
69 const locales = result.data && Array. isArray (result.data.shopLocales) ? result.data.shopLocales : null ;
70 if ( ! locales) return { primaryLanguage: { reason: 'unreadable' , evidence } };
71 const primary = locales. find (( entry ) => entry && entry.primary === true );
72 const { language , locale } = splitLocale (primary && primary.locale);
73 return { primaryLanguage: primary && language ? { value: language, locale, evidence } : { reason: 'not-exposed' , evidence } };
74 }
75
76 function countryField ( result ) {
77 const evidence = 'shop.billingAddress.countryCodeV2' ;
78 if (result.errors && result.errors. length ) return unknownFrom (result.errors, evidence);
79 const code = at (result.data, [ 'shop' , 'billingAddress' , 'countryCodeV2' ]);
80 return code ? { value: code, evidence } : { reason: 'not-exposed' , evidence };
81 }
82
83 // Counts orders by the currency of their `shopMoney` amounts -- the shop currency, which is what
84 // the import writes -- and lists the distinct currencies customers paid in (`presentmentCurrencyCode`).
85 // Counts only: no per-order record is kept.
86 function tallyOrderCurrencies ( orders ) {
87 const counts = {};
88 const presentment = new Set ();
89 for ( const order of orders) {
90 const shop = at (order, [ 'totalPriceSet' , 'shopMoney' , 'currencyCode' ]);
91 if ( typeof shop !== 'string' || ! shop) return null ;
92 counts[shop] = (counts[shop] || 0 ) + 1 ;
93 if ( typeof order.presentmentCurrencyCode === 'string' && order.presentmentCurrencyCode) presentment. add (order.presentmentCurrencyCode);
94 }
95 return { counts, presentmentCurrencies: [ ... presentment]. sort () };
96 }
97
98 function shopFields ( record ) {
99 const shop = record && Array. isArray (record.samples) ? record.samples[ 0 ] : null ;
100 if (record && record.errors && record.errors. length ) {
101 return { currency: unknownFrom (record.errors, 'shop.currencyCode' ), timeZone: unknownFrom (record.errors, 'shop.ianaTimezone' ) };
102 }
103 return {
104 currency: shop && shop.currencyCode ? { value: shop.currencyCode, evidence: 'shop.currencyCode' } : { reason: 'not-exposed' , evidence: 'shop.currencyCode' },
105 timeZone: shop && shop.ianaTimezone ? { value: shop.ianaTimezone, evidence: 'shop.ianaTimezone' } : { reason: 'not-exposed' , evidence: 'shop.ianaTimezone' },
106 };
107 }
108
109 // Discovery step: reads the two regional queries, joins them with the recorded `shop` and `orders`
110 // captures, and writes `<raw dir>/source-locale.json` for promotion.
111 async function regionalStep ( ctx ) {
112 const locales = await readQuery (ctx.execute, LOCALES_READ .query);
113 const country = await readQuery (ctx.execute, COUNTRY_READ .query);
114 const file = buildSourceRegional ({
115 adapter: 'shopify' ,
116 generatedAt: ctx. now (),
117 ... shopFields ( await readCaptured (ctx.rawDir, 'shop' )),
118 ... localeFields (locales),
119 country: countryField (country),
120 });
121 await writeAdapterSourceRegional (ctx.projectDir, 'shopify' , file);
122 const unknown = Object. entries (file). filter (([, row ]) => row && row.state === 'unknown' ). map (([ field , row ]) => `${ field }:${ row . reason }` );
123 return { file: 'source-locale.json' , unknown };
124 }
125
126 module . exports = { regionalStep, tallyOrderCurrencies, reasonFor };