@foxtware/mineral 0.1.16 → 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/etsyListingInventoryUpdate.js +11 -11
- 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 +167 -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 +1 -0
- package/api/starshipit/starshipitOrderGet.js +2 -10
- package/api/utils.js +12 -4
- package/hosting/deployFromHostingYml.js +2 -2
- package/hosting/execCommand.js +19 -2
- 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;
|
|
@@ -141,7 +141,7 @@ const deployFunction = async ({
|
|
|
141
141
|
console.log(deployCommand);
|
|
142
142
|
|
|
143
143
|
try {
|
|
144
|
-
await execCommand(deployCommand);
|
|
144
|
+
await execCommand(deployCommand, { interactive: true });
|
|
145
145
|
} catch (error) {
|
|
146
146
|
console.error(`Error deploying function ${ functionName }:`, error);
|
|
147
147
|
return;
|
|
@@ -188,7 +188,7 @@ const deployFunction = async ({
|
|
|
188
188
|
].join(' ');
|
|
189
189
|
|
|
190
190
|
console.log(schedulerCommand);
|
|
191
|
-
await execCommand(schedulerCommand);
|
|
191
|
+
await execCommand(schedulerCommand, { interactive: true });
|
|
192
192
|
} catch (error) {
|
|
193
193
|
console.error(`Error handling scheduler job ${ jobName }:`, error);
|
|
194
194
|
}
|
package/hosting/execCommand.js
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
|
|
3
|
-
const execCommand = (command) => new Promise((resolve, reject) => {
|
|
3
|
+
const execCommand = (command, { interactive = false } = {}) => new Promise((resolve, reject) => {
|
|
4
4
|
const childProcess = spawn(command, [], {
|
|
5
|
-
stdio: 'pipe',
|
|
5
|
+
stdio: interactive ? 'inherit' : 'pipe',
|
|
6
6
|
shell: true,
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
+
if (interactive) {
|
|
10
|
+
childProcess.on('close', (code) => {
|
|
11
|
+
if (code === 0) {
|
|
12
|
+
resolve({ stdout: '', stderr: '', command });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
reject({ stdout: '', stderr: '', command, code });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
childProcess.on('error', (error) => {
|
|
20
|
+
reject({ error, command });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
9
26
|
let stdout = '';
|
|
10
27
|
let stderr = '';
|
|
11
28
|
|
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',
|