@foxtware/mineral 0.1.17 → 0.1.18
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/.creds.yml.sample +16 -7
- package/AGENTS.md +3 -0
- package/_build_scripts/publish.js +15 -1
- package/api/backblaze/backblaze.constants.js +11 -0
- package/api/backblaze/backblaze.utils.js +322 -0
- package/api/backblaze/backblazeBucketsGet.js +68 -0
- package/api/backblaze/backblazeFileDelete.js +62 -0
- package/api/backblaze/backblazeFileHide.js +62 -0
- package/api/backblaze/backblazeFileUpload.js +142 -0
- package/api/backblaze/backblazeFilesGet.js +85 -0
- package/api/backblaze/backblazeGet.js +139 -0
- package/api/backblaze/docs.md +6 -0
- package/api/etsy/etsyListingUpdate.js +1 -1
- package/api/gorgias/gorgias.constants.js +8 -0
- package/api/gorgias/gorgias.utils.js +46 -0
- package/api/gorgias/gorgiasAccountGet.js +51 -0
- package/api/gorgias/gorgiasCustomerGet.js +70 -0
- package/api/gorgias/gorgiasCustomersGet.js +56 -0
- package/api/gorgias/gorgiasGet.js +150 -0
- package/api/gorgias/gorgiasGetSingle.js +26 -0
- package/api/gorgias/gorgiasMessagesGet.js +56 -0
- package/api/gorgias/gorgiasTagsGet.js +52 -0
- package/api/gorgias/gorgiasTicketCreate.js +66 -0
- package/api/gorgias/gorgiasTicketGet.js +70 -0
- package/api/gorgias/gorgiasTicketMessageCreate.js +65 -0
- package/api/gorgias/gorgiasTicketMessagesGet.js +66 -0
- package/api/gorgias/gorgiasTicketUpdate.js +62 -0
- package/api/gorgias/gorgiasTicketsGet.js +58 -0
- package/api/gorgias/gorgiasUsersGet.js +52 -0
- package/api/gorgias/gorgiasViewItemsGet.js +57 -0
- package/api/gorgias/gorgiasViewsGet.js +72 -0
- package/api/linear/docs.md +6 -0
- package/api/linear/linear.constants.js +3 -0
- package/api/linear/linear.utils.js +59 -0
- package/api/linear/linearAttachmentCreate.js +77 -0
- package/api/linear/linearAttachmentDelete.js +68 -0
- package/api/linear/linearCommentCreate.js +77 -0
- package/api/linear/linearCommentDelete.js +68 -0
- package/api/linear/linearCommentGet.js +77 -0
- package/api/linear/linearCommentUpdate.js +87 -0
- package/api/linear/linearCommentsGet.js +70 -0
- package/api/linear/linearCyclesGet.js +67 -0
- package/api/linear/linearGet.js +218 -0
- package/api/linear/linearIssueAddLabel.js +77 -0
- package/api/linear/linearIssueArchive.js +74 -0
- package/api/linear/linearIssueCreate.js +94 -0
- package/api/linear/linearIssueDelete.js +74 -0
- package/api/linear/linearIssueGet.js +87 -0
- package/api/linear/linearIssueLabelCreate.js +81 -0
- package/api/linear/linearIssueLabelsGet.js +66 -0
- package/api/linear/linearIssueRemoveLabel.js +77 -0
- package/api/linear/linearIssueUpdate.js +96 -0
- package/api/linear/linearIssuesGet.js +39 -63
- package/api/linear/linearOrganizationGet.js +64 -0
- package/api/linear/linearProjectCreate.js +92 -0
- package/api/linear/linearProjectGet.js +74 -0
- package/api/linear/linearProjectUpdate.js +80 -0
- package/api/linear/linearProjectsGet.js +71 -0
- package/api/linear/linearTeamGet.js +71 -0
- package/api/linear/linearTeamsGet.js +66 -0
- package/api/linear/linearUserGet.js +72 -0
- package/api/linear/linearUsersGet.js +71 -0
- package/api/linear/linearViewerGet.js +65 -0
- package/api/linear/linearWebhookCreate.js +77 -0
- package/api/linear/linearWebhookDelete.js +68 -0
- package/api/linear/linearWebhooksGet.js +67 -0
- package/api/linear/linearWorkflowStatesGet.js +73 -0
- package/api/pipe17/pipe17.utils.js +7 -20
- package/api/pipe17/pipe17ArrivalGet.js +1 -1
- package/api/pipe17/pipe17FulfillmentGet.js +1 -1
- package/api/pipe17/pipe17InventoryItemGet.js +1 -1
- package/api/pipe17/pipe17JobGet.js +11 -24
- package/api/pipe17/pipe17LocationGet.js +1 -1
- package/api/pipe17/pipe17OrderGet.js +1 -1
- package/api/pipe17/pipe17ProductGet.js +1 -1
- package/api/pipe17/pipe17PurchaseGet.js +1 -1
- package/api/pipe17/pipe17ReceiptGet.js +1 -1
- package/api/pipe17/pipe17ReturnGet.js +1 -1
- package/api/shopify/shopifyFileCreate.js +107 -0
- package/api/shopify/shopifyMetafieldDefinitionCreate.js +2 -0
- package/api/shopify/shopifyMetafieldDefinitionGet.js +108 -0
- package/api/shopify/shopifyMetafieldDefinitionPropagate.js +27 -71
- package/api/shopify/shopifyThemeFileGet.js +112 -0
- package/api/shopify/shopifyThemeFilePropagate.js +410 -0
- package/api/shopify/shopifyThemeFilesUpsert.js +88 -0
- package/api/starshipit/starshipit.utils.js +1 -0
- package/api/starshipit/starshipitOrderGet.js +2 -10
- package/api/utils.js +12 -4
- package/package.json +1 -1
- package/server.utils.js +1 -1
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
// https://shopify.dev/docs/api/admin-graphql/latest/queries/theme
|
|
2
|
+
// https://shopify.dev/docs/api/admin-graphql/latest/mutations/themeFilesUpsert
|
|
3
|
+
// https://shopify.dev/docs/api/admin-graphql/latest/mutations/fileCreate
|
|
4
|
+
|
|
5
|
+
const { credsValidator } = require('../validators');
|
|
6
|
+
const { ArgsWarden, wait } = require('../utils');
|
|
7
|
+
const { shopifyThemeFileGet } = require('./shopifyThemeFileGet');
|
|
8
|
+
const { shopifyThemeFilesUpsert } = require('./shopifyThemeFilesUpsert');
|
|
9
|
+
const { shopifyFilesGet } = require('./shopifyFilesGet');
|
|
10
|
+
const { shopifyFileCreate } = require('./shopifyFileCreate');
|
|
11
|
+
|
|
12
|
+
const argsWarden = new ArgsWarden([
|
|
13
|
+
['fromCredsPayload', credsValidator],
|
|
14
|
+
['toCredsPayload', credsValidator],
|
|
15
|
+
['fromThemeId'],
|
|
16
|
+
['toThemeId'],
|
|
17
|
+
['filepath'],
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const fileLookupAttrs = `
|
|
21
|
+
id
|
|
22
|
+
alt
|
|
23
|
+
fileStatus
|
|
24
|
+
preview {
|
|
25
|
+
image {
|
|
26
|
+
url
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
... on MediaImage {
|
|
30
|
+
image {
|
|
31
|
+
url
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
... on GenericFile {
|
|
35
|
+
url
|
|
36
|
+
}
|
|
37
|
+
... on Video {
|
|
38
|
+
originalSource {
|
|
39
|
+
url
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
`.trim();
|
|
43
|
+
|
|
44
|
+
const ASSET_LINK_REGEX = /shopify:\/\/shop_(?:images|videos|files)\/[^"'\\\s]+|https?:\/\/cdn\.shopify\.com\/[^"'\\\s]+/g;
|
|
45
|
+
|
|
46
|
+
const themeFileTextContent = (themeFile) => {
|
|
47
|
+
const body = themeFile?.body;
|
|
48
|
+
if (!body) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (body.content != null) {
|
|
52
|
+
return body.content;
|
|
53
|
+
}
|
|
54
|
+
if (body.contentBase64 != null) {
|
|
55
|
+
return Buffer.from(body.contentBase64, 'base64').toString('utf8');
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const filenameFromAssetLink = (assetLink) => {
|
|
61
|
+
const withoutQuery = assetLink.split('?')[0];
|
|
62
|
+
return decodeURIComponent(withoutQuery.split('/').pop());
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const schemeFromAssetLink = (assetLink) => {
|
|
66
|
+
const shopifySchemeMatch = assetLink.match(/^shopify:\/\/(shop_(?:images|videos|files))\//);
|
|
67
|
+
if (shopifySchemeMatch) {
|
|
68
|
+
return shopifySchemeMatch[1];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const filename = filenameFromAssetLink(assetLink).toLowerCase();
|
|
72
|
+
if (/\.(mp4|mov|webm|m4v)$/.test(filename)) {
|
|
73
|
+
return 'shop_videos';
|
|
74
|
+
}
|
|
75
|
+
if (/\.(pdf|txt|csv|json|zip|xml)$/.test(filename)) {
|
|
76
|
+
return 'shop_files';
|
|
77
|
+
}
|
|
78
|
+
return 'shop_images';
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const shopifyAssetUrl = (scheme, filename) => `shopify://${ scheme }/${ filename }`;
|
|
82
|
+
|
|
83
|
+
const fileSourceUrl = (file) => (
|
|
84
|
+
file?.image?.url
|
|
85
|
+
|| file?.url
|
|
86
|
+
|| file?.originalSource?.url
|
|
87
|
+
|| file?.preview?.image?.url
|
|
88
|
+
|| null
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const destinationUrlForAsset = (assetLink, filename, file) => {
|
|
92
|
+
if (assetLink.startsWith('shopify://')) {
|
|
93
|
+
return shopifyAssetUrl(schemeFromAssetLink(assetLink), filename);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return fileSourceUrl(file) || shopifyAssetUrl(schemeFromAssetLink(assetLink), filename);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const findFileByFilename = async (
|
|
100
|
+
credsPayload,
|
|
101
|
+
filename,
|
|
102
|
+
{
|
|
103
|
+
apiVersion,
|
|
104
|
+
} = {},
|
|
105
|
+
) => {
|
|
106
|
+
const response = await shopifyFilesGet(credsPayload, {
|
|
107
|
+
apiVersion,
|
|
108
|
+
attrs: fileLookupAttrs,
|
|
109
|
+
queries: [`filename:"${ filename }"`],
|
|
110
|
+
limit: 10,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!response.ok) {
|
|
114
|
+
return response;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const files = response.data || [];
|
|
118
|
+
const exactMatch = files.find((file) => {
|
|
119
|
+
const sourceUrl = fileSourceUrl(file);
|
|
120
|
+
if (!sourceUrl) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
return filenameFromAssetLink(sourceUrl) === filename;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
data: exactMatch || files[0] || null,
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const waitForFileReady = async (
|
|
133
|
+
credsPayload,
|
|
134
|
+
filename,
|
|
135
|
+
{
|
|
136
|
+
apiVersion,
|
|
137
|
+
attempts = 8,
|
|
138
|
+
intervalMs = 1500,
|
|
139
|
+
} = {},
|
|
140
|
+
) => {
|
|
141
|
+
let file = null;
|
|
142
|
+
|
|
143
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
144
|
+
if (attempt > 0) {
|
|
145
|
+
await wait(intervalMs);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const lookupResponse = await findFileByFilename(credsPayload, filename, { apiVersion });
|
|
149
|
+
if (!lookupResponse.ok) {
|
|
150
|
+
return lookupResponse;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
file = lookupResponse.data;
|
|
154
|
+
if (file?.fileStatus === 'READY' && fileSourceUrl(file)) {
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
data: file,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (file?.fileStatus === 'FAILED') {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
error: {
|
|
165
|
+
code: 'FILE_PROCESSING_FAILED',
|
|
166
|
+
message: `Uploaded file "${ filename }" failed processing.`,
|
|
167
|
+
details: file,
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
ok: true,
|
|
175
|
+
data: file,
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const resolveAssetOnToStore = async (
|
|
180
|
+
fromCredsPayload,
|
|
181
|
+
toCredsPayload,
|
|
182
|
+
assetLink,
|
|
183
|
+
{
|
|
184
|
+
apiVersion,
|
|
185
|
+
} = {},
|
|
186
|
+
) => {
|
|
187
|
+
const filename = filenameFromAssetLink(assetLink);
|
|
188
|
+
|
|
189
|
+
const toFileResponse = await findFileByFilename(toCredsPayload, filename, { apiVersion });
|
|
190
|
+
if (!toFileResponse.ok) {
|
|
191
|
+
return toFileResponse;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (toFileResponse.data) {
|
|
195
|
+
return {
|
|
196
|
+
ok: true,
|
|
197
|
+
data: {
|
|
198
|
+
fromUrl: assetLink,
|
|
199
|
+
toUrl: destinationUrlForAsset(assetLink, filename, toFileResponse.data),
|
|
200
|
+
filename,
|
|
201
|
+
uploaded: false,
|
|
202
|
+
file: toFileResponse.data,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
let originalSource = assetLink.startsWith('http') ? assetLink : null;
|
|
208
|
+
|
|
209
|
+
if (!originalSource) {
|
|
210
|
+
const fromFileResponse = await findFileByFilename(fromCredsPayload, filename, { apiVersion });
|
|
211
|
+
if (!fromFileResponse.ok) {
|
|
212
|
+
return fromFileResponse;
|
|
213
|
+
}
|
|
214
|
+
if (!fromFileResponse.data) {
|
|
215
|
+
return {
|
|
216
|
+
ok: false,
|
|
217
|
+
error: {
|
|
218
|
+
code: 'ASSET_NOT_FOUND',
|
|
219
|
+
message: `Asset "${ filename }" not found in Files on the from store.`,
|
|
220
|
+
details: { assetLink, filename },
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
originalSource = fileSourceUrl(fromFileResponse.data);
|
|
225
|
+
if (!originalSource) {
|
|
226
|
+
return {
|
|
227
|
+
ok: false,
|
|
228
|
+
error: {
|
|
229
|
+
code: 'ASSET_URL_MISSING',
|
|
230
|
+
message: `No downloadable URL for asset "${ filename }" on the from store.`,
|
|
231
|
+
details: { assetLink, filename, file: fromFileResponse.data },
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const createResponse = await shopifyFileCreate(
|
|
238
|
+
toCredsPayload,
|
|
239
|
+
{
|
|
240
|
+
filename,
|
|
241
|
+
originalSource,
|
|
242
|
+
},
|
|
243
|
+
{ apiVersion },
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
if (!createResponse.ok) {
|
|
247
|
+
return createResponse;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const readyResponse = await waitForFileReady(toCredsPayload, filename, { apiVersion });
|
|
251
|
+
if (!readyResponse.ok) {
|
|
252
|
+
return readyResponse;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const createdFile = readyResponse.data
|
|
256
|
+
|| (
|
|
257
|
+
Array.isArray(createResponse.data?.files)
|
|
258
|
+
? createResponse.data.files[0]
|
|
259
|
+
: createResponse.data?.files
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
ok: true,
|
|
264
|
+
data: {
|
|
265
|
+
fromUrl: assetLink,
|
|
266
|
+
toUrl: destinationUrlForAsset(assetLink, filename, createdFile),
|
|
267
|
+
filename,
|
|
268
|
+
uploaded: true,
|
|
269
|
+
originalSource,
|
|
270
|
+
file: createdFile,
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const shopifyThemeFilePropagate = async (
|
|
276
|
+
fromCredsPayload,
|
|
277
|
+
toCredsPayload,
|
|
278
|
+
fromThemeId,
|
|
279
|
+
toThemeId,
|
|
280
|
+
filepath,
|
|
281
|
+
{
|
|
282
|
+
apiVersion,
|
|
283
|
+
} = {},
|
|
284
|
+
) => {
|
|
285
|
+
|
|
286
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
287
|
+
fromCredsPayload,
|
|
288
|
+
toCredsPayload,
|
|
289
|
+
fromThemeId,
|
|
290
|
+
toThemeId,
|
|
291
|
+
filepath,
|
|
292
|
+
});
|
|
293
|
+
if (rejectResponse) {
|
|
294
|
+
return rejectResponse;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const fromFileResponse = await shopifyThemeFileGet(
|
|
298
|
+
fromCredsPayload,
|
|
299
|
+
fromThemeId,
|
|
300
|
+
filepath,
|
|
301
|
+
{ apiVersion },
|
|
302
|
+
);
|
|
303
|
+
if (!fromFileResponse.ok) {
|
|
304
|
+
return fromFileResponse;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const fromThemeFile = fromFileResponse.data.find((file) => file?.filename === filepath)
|
|
308
|
+
|| fromFileResponse.data[0];
|
|
309
|
+
|
|
310
|
+
if (!fromThemeFile) {
|
|
311
|
+
return {
|
|
312
|
+
ok: false,
|
|
313
|
+
error: {
|
|
314
|
+
code: 'NOT_FOUND',
|
|
315
|
+
message: `Theme file "${ filepath }" not found on from theme.`,
|
|
316
|
+
details: { fromThemeId, filepath },
|
|
317
|
+
},
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const content = themeFileTextContent(fromThemeFile);
|
|
322
|
+
if (content == null) {
|
|
323
|
+
return {
|
|
324
|
+
ok: false,
|
|
325
|
+
error: {
|
|
326
|
+
code: 'UNSUPPORTED_BODY',
|
|
327
|
+
message: `Theme file "${ filepath }" has no text body to propagate.`,
|
|
328
|
+
details: fromThemeFile,
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const assetLinks = [...new Set(content.match(ASSET_LINK_REGEX) || [])];
|
|
334
|
+
const assetMappings = [];
|
|
335
|
+
|
|
336
|
+
for (const assetLink of assetLinks) {
|
|
337
|
+
const resolveResponse = await resolveAssetOnToStore(
|
|
338
|
+
fromCredsPayload,
|
|
339
|
+
toCredsPayload,
|
|
340
|
+
assetLink,
|
|
341
|
+
{ apiVersion },
|
|
342
|
+
);
|
|
343
|
+
if (!resolveResponse.ok) {
|
|
344
|
+
return resolveResponse;
|
|
345
|
+
}
|
|
346
|
+
assetMappings.push(resolveResponse.data);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
let translatedContent = content;
|
|
350
|
+
for (const { fromUrl, toUrl } of assetMappings) {
|
|
351
|
+
if (fromUrl === toUrl) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
translatedContent = translatedContent.split(fromUrl).join(toUrl);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const upsertResponse = await shopifyThemeFilesUpsert(
|
|
358
|
+
toCredsPayload,
|
|
359
|
+
toThemeId,
|
|
360
|
+
{
|
|
361
|
+
filename: filepath,
|
|
362
|
+
body: {
|
|
363
|
+
type: 'TEXT',
|
|
364
|
+
value: translatedContent,
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
{ apiVersion },
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
if (!upsertResponse.ok) {
|
|
371
|
+
return upsertResponse;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
ok: true,
|
|
376
|
+
data: {
|
|
377
|
+
filepath,
|
|
378
|
+
fromThemeId,
|
|
379
|
+
toThemeId,
|
|
380
|
+
assetMappings: assetMappings.map(({ fromUrl, toUrl, filename, uploaded }) => ({
|
|
381
|
+
fromUrl,
|
|
382
|
+
toUrl,
|
|
383
|
+
filename,
|
|
384
|
+
uploaded,
|
|
385
|
+
})),
|
|
386
|
+
upsert: upsertResponse.data,
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const funcApiConfig = {
|
|
392
|
+
argsWarden,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
module.exports = {
|
|
396
|
+
shopifyThemeFilePropagate,
|
|
397
|
+
funcApiConfig,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
/*
|
|
401
|
+
curl -X POST "http://localhost:8000/shopifyThemeFilePropagate" \
|
|
402
|
+
-H "Content-Type: application/json" \
|
|
403
|
+
-d '{
|
|
404
|
+
"fromCredsPayload": { "credsPath": "shopify.au" },
|
|
405
|
+
"toCredsPayload": { "credsPath": "shopify.us" },
|
|
406
|
+
"fromThemeId": "1234",
|
|
407
|
+
"toThemeId": "5678",
|
|
408
|
+
"filepath": "templates/page.sign_up_beauty.json"
|
|
409
|
+
}'
|
|
410
|
+
*/
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// https://shopify.dev/docs/api/admin-graphql/latest/mutations/themeFilesUpsert
|
|
2
|
+
|
|
3
|
+
const { credsValidator } = require('../validators');
|
|
4
|
+
const { ArgsWarden, ensureArray, everyIfArray } = require('../utils');
|
|
5
|
+
const { shopifyMutationDo } = require('./shopifyMutationDo');
|
|
6
|
+
|
|
7
|
+
const themeFileInputValidator = ({
|
|
8
|
+
filename,
|
|
9
|
+
body,
|
|
10
|
+
} = {}) => Boolean(filename && body?.type && body?.value != null);
|
|
11
|
+
|
|
12
|
+
const argsWarden = new ArgsWarden([
|
|
13
|
+
['credsPayload', credsValidator],
|
|
14
|
+
['themeId'],
|
|
15
|
+
['files', (files) => everyIfArray(themeFileInputValidator, files)],
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const shopifyThemeFilesUpsert = async (
|
|
19
|
+
credsPayload,
|
|
20
|
+
themeId,
|
|
21
|
+
files,
|
|
22
|
+
{
|
|
23
|
+
apiVersion,
|
|
24
|
+
returnSchema = `
|
|
25
|
+
upsertedThemeFiles {
|
|
26
|
+
filename
|
|
27
|
+
}
|
|
28
|
+
job {
|
|
29
|
+
id
|
|
30
|
+
done
|
|
31
|
+
}
|
|
32
|
+
`.trim(),
|
|
33
|
+
} = {},
|
|
34
|
+
) => {
|
|
35
|
+
|
|
36
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
37
|
+
credsPayload,
|
|
38
|
+
themeId,
|
|
39
|
+
files,
|
|
40
|
+
});
|
|
41
|
+
if (rejectResponse) {
|
|
42
|
+
return rejectResponse;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return shopifyMutationDo(
|
|
46
|
+
credsPayload,
|
|
47
|
+
'themeFilesUpsert',
|
|
48
|
+
{
|
|
49
|
+
mutationVariables: {
|
|
50
|
+
themeId: {
|
|
51
|
+
type: 'ID!',
|
|
52
|
+
value: `gid://shopify/OnlineStoreTheme/${ themeId }`,
|
|
53
|
+
},
|
|
54
|
+
files: {
|
|
55
|
+
type: '[OnlineStoreThemeFilesUpsertFileInput!]!',
|
|
56
|
+
value: ensureArray(files),
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
returnSchema,
|
|
60
|
+
apiVersion,
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const funcApiConfig = {
|
|
66
|
+
argsWarden,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
shopifyThemeFilesUpsert,
|
|
71
|
+
funcApiConfig,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
curl -X POST "http://localhost:8000/shopifyThemeFilesUpsert" \
|
|
76
|
+
-H "Content-Type: application/json" \
|
|
77
|
+
-d '{
|
|
78
|
+
"credsPayload": { "credsPath": "shopify.au" },
|
|
79
|
+
"themeId": "129840808008",
|
|
80
|
+
"files": {
|
|
81
|
+
"filename": "templates/index.json",
|
|
82
|
+
"body": {
|
|
83
|
+
"type": "TEXT",
|
|
84
|
+
"value": "{ \"sections\": {}, \"order\": [] }"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}'
|
|
88
|
+
*/
|
|
@@ -32,7 +32,7 @@ const starshipitOrderGet = async (
|
|
|
32
32
|
orderNumber,
|
|
33
33
|
} = orderIdentifier;
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
return starshipitClient.fetch({
|
|
36
36
|
requestPayload: {
|
|
37
37
|
url: '/orders',
|
|
38
38
|
params: {
|
|
@@ -42,17 +42,9 @@ const starshipitOrderGet = async (
|
|
|
42
42
|
},
|
|
43
43
|
context: {
|
|
44
44
|
credsPayload,
|
|
45
|
+
resultPath: 'order',
|
|
45
46
|
},
|
|
46
47
|
});
|
|
47
|
-
|
|
48
|
-
if (!response?.ok) {
|
|
49
|
-
return response;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return {
|
|
53
|
-
...response,
|
|
54
|
-
data: response.data?.order ?? response.data,
|
|
55
|
-
};
|
|
56
48
|
};
|
|
57
49
|
|
|
58
50
|
const funcApiConfig = {
|
package/api/utils.js
CHANGED
|
@@ -22,6 +22,8 @@ const sentenceCaseString = (string) => `${ string[0].toLowerCase() }${ string.sl
|
|
|
22
22
|
|
|
23
23
|
const normalise = (value) => (value || '').toString().trim().toLowerCase();
|
|
24
24
|
|
|
25
|
+
const valueProvided = (value) => value !== undefined && value !== null;
|
|
26
|
+
|
|
25
27
|
const credsByPath = (credsPath, credsObject) => {
|
|
26
28
|
const pathNodes = pathAsArray(credsPath);
|
|
27
29
|
|
|
@@ -166,6 +168,10 @@ const bodyPassesThrough = (body) => {
|
|
|
166
168
|
return true;
|
|
167
169
|
}
|
|
168
170
|
|
|
171
|
+
if (Buffer.isBuffer(body)) {
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
|
|
169
175
|
return typeof body.getHeaders === 'function';
|
|
170
176
|
};
|
|
171
177
|
|
|
@@ -394,6 +400,10 @@ const stripGraphqlEdgesAndNodes = (value) => {
|
|
|
394
400
|
return value.edges.map((edge) => stripGraphqlEdgesAndNodes(edge?.node));
|
|
395
401
|
}
|
|
396
402
|
|
|
403
|
+
if (Array.isArray(value.nodes)) {
|
|
404
|
+
return value.nodes.map(stripGraphqlEdgesAndNodes);
|
|
405
|
+
}
|
|
406
|
+
|
|
397
407
|
const output = {};
|
|
398
408
|
for (const [key, nestedValue] of Object.entries(value)) {
|
|
399
409
|
output[key] = stripGraphqlEdgesAndNodes(nestedValue);
|
|
@@ -648,14 +658,14 @@ const fetchClientCommonSteps = {
|
|
|
648
658
|
const { resultPath } = context;
|
|
649
659
|
const { data } = response;
|
|
650
660
|
|
|
651
|
-
if (!data || !resultPath) {
|
|
661
|
+
if (!valueProvided(data) || !valueProvided(resultPath)) {
|
|
652
662
|
return {};
|
|
653
663
|
}
|
|
654
664
|
|
|
655
665
|
const resultPathNodes = pathAsArray(resultPath);
|
|
656
666
|
const dataAtPath = objectDigNodeAtPath(data, resultPathNodes);
|
|
657
667
|
|
|
658
|
-
if (
|
|
668
|
+
if (dataAtPath === undefined) {
|
|
659
669
|
return {
|
|
660
670
|
response: {
|
|
661
671
|
ok: false,
|
|
@@ -676,8 +686,6 @@ const fetchClientCommonSteps = {
|
|
|
676
686
|
},
|
|
677
687
|
};
|
|
678
688
|
|
|
679
|
-
const valueProvided = (value) => value !== undefined && value !== null;
|
|
680
|
-
|
|
681
689
|
const ensureArray = (value) => {
|
|
682
690
|
if (Array.isArray(value)) {
|
|
683
691
|
return value;
|
package/package.json
CHANGED
package/server.utils.js
CHANGED
|
@@ -3,7 +3,7 @@ const { HOSTED } = require('./api/constants');
|
|
|
3
3
|
const { StringDecoder } = require('string_decoder');
|
|
4
4
|
|
|
5
5
|
const respondJson = (res, statusCode, payload) => {
|
|
6
|
-
|
|
6
|
+
!HOSTED && logDeep(payload);
|
|
7
7
|
const body = JSON.stringify(payload);
|
|
8
8
|
res.writeHead(statusCode, {
|
|
9
9
|
'Content-Type': 'application/json',
|