@foxtware/mineral 0.1.17 → 0.1.19
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 +4 -0
- package/_build_scripts/createNewFunction.js +10 -5
- 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/peoplevox/peoplevox.utils.js +25 -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/shopifyFulfillmentCreate.js +180 -0
- package/api/shopify/shopifyFulfillmentTrackingInfoUpdate.js +92 -0
- package/api/shopify/shopifyMetafieldDefinitionCreate.js +2 -0
- package/api/shopify/shopifyMetafieldDefinitionGet.js +108 -0
- package/api/shopify/shopifyMetafieldDefinitionPropagate.js +93 -86
- package/api/shopify/shopifyOrderFulfill.js +322 -0
- 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 +29 -3
- package/api/starshipit/starshipitOrderGet.js +2 -10
- package/api/starshipit/starshipitOrdersGet.js +96 -0
- package/api/utils.js +96 -5
- 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
|
+
*/
|
|
@@ -8,9 +8,19 @@ const {
|
|
|
8
8
|
const interpretStarshipitResponse = async (state) => {
|
|
9
9
|
const { response } = state;
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const responseData = response.data || {};
|
|
12
|
+
const {
|
|
13
|
+
success,
|
|
14
|
+
succeeded,
|
|
15
|
+
results,
|
|
16
|
+
errors,
|
|
17
|
+
data: nestedData,
|
|
18
|
+
...rest
|
|
19
|
+
} = responseData;
|
|
20
|
+
|
|
21
|
+
const ok = success === true || succeeded === true;
|
|
12
22
|
|
|
13
|
-
if (!
|
|
23
|
+
if (!ok) {
|
|
14
24
|
const firstError = errors?.[0];
|
|
15
25
|
|
|
16
26
|
// TODO: Consider removing data if errors
|
|
@@ -27,10 +37,25 @@ const interpretStarshipitResponse = async (state) => {
|
|
|
27
37
|
};
|
|
28
38
|
}
|
|
29
39
|
|
|
40
|
+
let data;
|
|
41
|
+
if (results !== undefined) {
|
|
42
|
+
data = results;
|
|
43
|
+
} else if (nestedData && typeof nestedData === 'object' && !Array.isArray(nestedData)) {
|
|
44
|
+
// List endpoints return { page_*, data: { orders|products }, succeeded }
|
|
45
|
+
data = {
|
|
46
|
+
...rest,
|
|
47
|
+
...nestedData,
|
|
48
|
+
...success !== undefined && { success },
|
|
49
|
+
...succeeded !== undefined && { succeeded },
|
|
50
|
+
};
|
|
51
|
+
} else {
|
|
52
|
+
data = responseData;
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
return {
|
|
31
56
|
response: {
|
|
32
57
|
...response,
|
|
33
|
-
data
|
|
58
|
+
data,
|
|
34
59
|
},
|
|
35
60
|
};
|
|
36
61
|
};
|
|
@@ -63,6 +88,7 @@ const starshipitClient = new FetchClient({
|
|
|
63
88
|
'fetch',
|
|
64
89
|
fetchClientCommonSteps.exitEarlyOnNotOk,
|
|
65
90
|
interpretStarshipitResponse,
|
|
91
|
+
fetchClientCommonSteps.digToPath,
|
|
66
92
|
],
|
|
67
93
|
});
|
|
68
94
|
|
|
@@ -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 = {
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// https://api-docs.starshipit.com/#0aef707f-e2f5-493a-a382-8235c00c9c18
|
|
2
|
+
|
|
3
|
+
const { ArgsWarden } = require('../utils');
|
|
4
|
+
const { credsValidator } = require('../validators');
|
|
5
|
+
const { starshipitGet, starshipitGetter } = require('../starshipit/starshipitGet');
|
|
6
|
+
|
|
7
|
+
const ORDERS_MAX_PER_PAGE = 500;
|
|
8
|
+
|
|
9
|
+
const argsWarden = new ArgsWarden([
|
|
10
|
+
['credsPayload', credsValidator],
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const starshipitOrdersGet = async (
|
|
14
|
+
returnGetter,
|
|
15
|
+
|
|
16
|
+
credsPayload,
|
|
17
|
+
{
|
|
18
|
+
orderId,
|
|
19
|
+
orderNumber,
|
|
20
|
+
status,
|
|
21
|
+
filter,
|
|
22
|
+
include,
|
|
23
|
+
sortColumn,
|
|
24
|
+
sortDirection,
|
|
25
|
+
perPage = ORDERS_MAX_PER_PAGE,
|
|
26
|
+
...getterOptions
|
|
27
|
+
} = {},
|
|
28
|
+
) => {
|
|
29
|
+
|
|
30
|
+
const rejectResponse = await argsWarden.responseIfRejectingArgs({
|
|
31
|
+
credsPayload,
|
|
32
|
+
});
|
|
33
|
+
if (rejectResponse) {
|
|
34
|
+
return rejectResponse;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getterArgs = [
|
|
38
|
+
credsPayload,
|
|
39
|
+
'/orders',
|
|
40
|
+
{
|
|
41
|
+
nodeName: 'orders',
|
|
42
|
+
perPage,
|
|
43
|
+
params: {
|
|
44
|
+
...orderId && { order_id: orderId },
|
|
45
|
+
...orderNumber && { order_number: orderNumber },
|
|
46
|
+
...status && { status },
|
|
47
|
+
...filter && { filter },
|
|
48
|
+
...include && { include },
|
|
49
|
+
...sortColumn && { sort_column: sortColumn },
|
|
50
|
+
...sortDirection && { sort_direction: sortDirection },
|
|
51
|
+
},
|
|
52
|
+
digester: (response) => {
|
|
53
|
+
if (!response?.ok) {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const { orders, order } = response.data || {};
|
|
57
|
+
if (Array.isArray(orders)) {
|
|
58
|
+
return orders;
|
|
59
|
+
}
|
|
60
|
+
if (order) {
|
|
61
|
+
return [order];
|
|
62
|
+
}
|
|
63
|
+
return [];
|
|
64
|
+
},
|
|
65
|
+
...getterOptions,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
return returnGetter
|
|
70
|
+
? starshipitGetter(...getterArgs)
|
|
71
|
+
: starshipitGet(...getterArgs);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const funcApiConfig = {
|
|
75
|
+
argsWarden,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
starshipitOrdersGet: (...args) => starshipitOrdersGet(false, ...args),
|
|
80
|
+
starshipitOrdersGetter: (...args) => starshipitOrdersGet(true, ...args),
|
|
81
|
+
funcApiConfig,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
curl -X POST "http://localhost:8000/starshipitOrdersGet" \
|
|
86
|
+
-H "Content-Type: application/json" \
|
|
87
|
+
-d '{
|
|
88
|
+
"credsPayload": { "credsPath": "starshipit.wf" },
|
|
89
|
+
"options": {
|
|
90
|
+
"status": "Shipped",
|
|
91
|
+
"include": ["Items", "Packages"],
|
|
92
|
+
"perPage": 2,
|
|
93
|
+
"limit": 2
|
|
94
|
+
}
|
|
95
|
+
}'
|
|
96
|
+
*/
|