tiendu 0.4.0 → 0.6.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/README.md +56 -13
- package/bin/tiendu.js +43 -16
- package/bin/tiendu.mjs +1 -136
- package/lib/api.mjs +82 -30
- package/lib/archive.mjs +30 -0
- package/lib/assets.mjs +245 -0
- package/lib/build.mjs +299 -41
- package/lib/dev.mjs +234 -144
- package/lib/fs-utils.mjs +35 -0
- package/lib/local-preview.mjs +393 -0
- package/lib/postcss.mjs +166 -0
- package/lib/preview.mjs +279 -73
- package/lib/publish.mjs +32 -17
- package/lib/pull.mjs +37 -12
- package/lib/push.mjs +60 -57
- package/lib/retry.mjs +69 -0
- package/package.json +2 -2
package/lib/preview.mjs
CHANGED
|
@@ -1,13 +1,54 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { loadConfigOrFail, writeConfig } from "./config.mjs";
|
|
3
|
-
import { apiFetch } from "./api.mjs";
|
|
3
|
+
import { apiFetch, fetchPreview } from "./api.mjs";
|
|
4
4
|
|
|
5
|
-
const buildPreviewUrl = (apiBaseUrl, previewHostname) => {
|
|
5
|
+
export const buildPreviewUrl = (apiBaseUrl, previewHostname) => {
|
|
6
6
|
const base = new URL(apiBaseUrl);
|
|
7
7
|
const hasExplicitPort = previewHostname.includes(":");
|
|
8
8
|
return `${base.protocol}//${previewHostname}${!hasExplicitPort && base.port ? `:${base.port}` : ""}/`;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
const formatShortDateTime = (value) => {
|
|
12
|
+
if (!value) return "Unknown";
|
|
13
|
+
const date = new Date(value);
|
|
14
|
+
if (Number.isNaN(date.getTime())) return "Unknown";
|
|
15
|
+
|
|
16
|
+
const months = [
|
|
17
|
+
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
18
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
19
|
+
];
|
|
20
|
+
const month = months[date.getMonth()];
|
|
21
|
+
const day = date.getDate();
|
|
22
|
+
const hours = String(date.getHours()).padStart(2, "0");
|
|
23
|
+
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
24
|
+
return `${month} ${day} ${hours}:${minutes}`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const getPreviewDisplayName = (preview) =>
|
|
28
|
+
preview.name || `${formatShortDateTime(preview.createdAt)} preview (no name)`;
|
|
29
|
+
|
|
30
|
+
export const getPreviewUrl = (apiBaseUrl, preview) =>
|
|
31
|
+
buildPreviewUrl(apiBaseUrl, preview.previewHostname);
|
|
32
|
+
|
|
33
|
+
export const fetchPreviewDetails = async (
|
|
34
|
+
apiBaseUrl,
|
|
35
|
+
apiKey,
|
|
36
|
+
storeId,
|
|
37
|
+
previewKey,
|
|
38
|
+
) => {
|
|
39
|
+
const result = await fetchPreview(apiBaseUrl, apiKey, storeId, previewKey);
|
|
40
|
+
if (!result.ok) return result;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
ok: true,
|
|
44
|
+
data: {
|
|
45
|
+
preview: result.data,
|
|
46
|
+
displayName: getPreviewDisplayName(result.data),
|
|
47
|
+
url: getPreviewUrl(apiBaseUrl, result.data),
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
11
52
|
/**
|
|
12
53
|
* @param {Array<any>} previews
|
|
13
54
|
* @param {string | undefined} previewKey
|
|
@@ -42,18 +83,10 @@ export const createPreview = async (apiBaseUrl, apiKey, storeId, name) => {
|
|
|
42
83
|
`/api/v2/stores/${storeId}/theme-previews`,
|
|
43
84
|
{
|
|
44
85
|
method: "POST",
|
|
45
|
-
body: JSON.stringify({ name: name ?? "
|
|
86
|
+
body: JSON.stringify({ name: name ?? "" }),
|
|
46
87
|
},
|
|
47
88
|
);
|
|
48
89
|
|
|
49
|
-
if (response.status === 409) {
|
|
50
|
-
const body = await response.json().catch(() => ({}));
|
|
51
|
-
const message =
|
|
52
|
-
body?.error?.message ??
|
|
53
|
-
"A preview already exists for this store. Delete it first with: tiendu preview delete";
|
|
54
|
-
return { ok: false, error: message };
|
|
55
|
-
}
|
|
56
|
-
|
|
57
90
|
if (!response.ok) {
|
|
58
91
|
const body = await response.text().catch(() => "");
|
|
59
92
|
return {
|
|
@@ -158,6 +191,116 @@ export const publishPreview = async (
|
|
|
158
191
|
}
|
|
159
192
|
};
|
|
160
193
|
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Shared interactive preview picker
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
const CREATE_NEW_VALUE = "__create_new__";
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Interactively resolve a preview key. Uses attached key if valid, otherwise
|
|
202
|
+
* prompts the user to pick an existing preview or create a new one.
|
|
203
|
+
*
|
|
204
|
+
* @param {{ config: import("./config.mjs").TienduConfig, credentials: import("./config.mjs").TienduCredentials }} opts
|
|
205
|
+
* @returns {Promise<string>} The resolved preview key
|
|
206
|
+
*/
|
|
207
|
+
export const resolvePreviewKeyInteractively = async ({ config, credentials }) => {
|
|
208
|
+
const { apiBaseUrl, storeId } = config;
|
|
209
|
+
const { apiKey } = credentials;
|
|
210
|
+
|
|
211
|
+
// 1. Validate stored key
|
|
212
|
+
if (config.previewKey) {
|
|
213
|
+
const result = await fetchPreview(apiBaseUrl, apiKey, storeId, config.previewKey);
|
|
214
|
+
if (result.ok) {
|
|
215
|
+
return config.previewKey;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
p.log.warn(`Stored preview ${config.previewKey} was not found. Please select a preview.`);
|
|
219
|
+
const { previewKey: _, ...rest } = config;
|
|
220
|
+
await writeConfig(rest);
|
|
221
|
+
} else {
|
|
222
|
+
p.log.warn("No preview attached.");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 2. List previews
|
|
226
|
+
const listResult = await listPreviews(apiBaseUrl, apiKey, storeId);
|
|
227
|
+
if (!listResult.ok) {
|
|
228
|
+
p.log.error(listResult.error);
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const previews = listResult.data;
|
|
233
|
+
|
|
234
|
+
if (previews.length === 0) {
|
|
235
|
+
p.log.info("No previews found for this store.");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 3. Show picker
|
|
239
|
+
const options = [
|
|
240
|
+
...previews.map((preview) => ({
|
|
241
|
+
value: preview.previewKey,
|
|
242
|
+
label: getPreviewDisplayName(preview),
|
|
243
|
+
hint: preview.previewKey,
|
|
244
|
+
})),
|
|
245
|
+
{
|
|
246
|
+
value: CREATE_NEW_VALUE,
|
|
247
|
+
label: "Create a new preview",
|
|
248
|
+
},
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
const selected = await p.select({
|
|
252
|
+
message: "Select a preview",
|
|
253
|
+
options,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
if (p.isCancel(selected)) {
|
|
257
|
+
p.cancel("Cancelled.");
|
|
258
|
+
process.exit(0);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 4. Handle create new
|
|
262
|
+
if (selected === CREATE_NEW_VALUE) {
|
|
263
|
+
const nameInput = await p.text({
|
|
264
|
+
message: "Preview name (optional)",
|
|
265
|
+
placeholder: "Press Enter to skip",
|
|
266
|
+
defaultValue: "",
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
if (p.isCancel(nameInput)) {
|
|
270
|
+
p.cancel("Cancelled.");
|
|
271
|
+
process.exit(0);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const name = (nameInput ?? "").trim();
|
|
275
|
+
const spinner = p.spinner();
|
|
276
|
+
spinner.start("Creating preview...");
|
|
277
|
+
|
|
278
|
+
const createResult = await createPreview(apiBaseUrl, apiKey, storeId, name);
|
|
279
|
+
if (!createResult.ok) {
|
|
280
|
+
spinner.stop("Failed to create preview.", 1);
|
|
281
|
+
p.log.error(createResult.error);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const preview = createResult.data;
|
|
286
|
+
const displayName = getPreviewDisplayName(preview);
|
|
287
|
+
const url = getPreviewUrl(apiBaseUrl, preview);
|
|
288
|
+
spinner.stop(`Preview "${displayName}" created (${preview.previewKey})`);
|
|
289
|
+
p.log.message(` ${url}`);
|
|
290
|
+
|
|
291
|
+
await writeConfig({ ...config, previewKey: preview.previewKey });
|
|
292
|
+
return preview.previewKey;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 5. Attach to selected preview
|
|
296
|
+
await writeConfig({ ...config, previewKey: selected });
|
|
297
|
+
const selectedPreview = previews.find((p) => p.previewKey === selected);
|
|
298
|
+
const displayName = selectedPreview ? getPreviewDisplayName(selectedPreview) : selected;
|
|
299
|
+
p.log.success(`Attached to "${displayName}" (${selected})`);
|
|
300
|
+
|
|
301
|
+
return selected;
|
|
302
|
+
};
|
|
303
|
+
|
|
161
304
|
// ---------------------------------------------------------------------------
|
|
162
305
|
// CLI commands
|
|
163
306
|
// ---------------------------------------------------------------------------
|
|
@@ -182,8 +325,10 @@ export const previewCreate = async (name) => {
|
|
|
182
325
|
}
|
|
183
326
|
|
|
184
327
|
const preview = result.data;
|
|
185
|
-
const url =
|
|
186
|
-
|
|
328
|
+
const url = getPreviewUrl(config.apiBaseUrl, preview);
|
|
329
|
+
const displayName = getPreviewDisplayName(preview);
|
|
330
|
+
spinner.stop(`Preview "${displayName}" created (${preview.previewKey})`);
|
|
331
|
+
p.log.message(` ${url}`);
|
|
187
332
|
|
|
188
333
|
await writeConfig({ ...config, previewKey: preview.previewKey });
|
|
189
334
|
};
|
|
@@ -215,14 +360,15 @@ export const previewList = async () => {
|
|
|
215
360
|
`${result.data.length} preview${result.data.length === 1 ? "" : "s"}:`,
|
|
216
361
|
);
|
|
217
362
|
|
|
218
|
-
const activePreview = resolveActivePreview(result.data, config.previewKey);
|
|
219
|
-
|
|
220
363
|
for (const preview of result.data) {
|
|
221
|
-
const
|
|
222
|
-
|
|
364
|
+
const isAttached = config.previewKey === preview.previewKey;
|
|
365
|
+
const indicator = isAttached ? " \u2190 attached" : "";
|
|
223
366
|
const url = buildPreviewUrl(config.apiBaseUrl, preview.previewHostname);
|
|
224
|
-
|
|
367
|
+
const displayName = getPreviewDisplayName(preview);
|
|
368
|
+
p.log.message(` ${displayName} ${url}${indicator}`);
|
|
225
369
|
}
|
|
370
|
+
|
|
371
|
+
p.log.info("Tip: run tiendu preview attach <key> to switch previews.");
|
|
226
372
|
};
|
|
227
373
|
|
|
228
374
|
const formatRelativeDate = (value) => {
|
|
@@ -245,67 +391,119 @@ const formatRelativeDate = (value) => {
|
|
|
245
391
|
export const previewShow = async () => {
|
|
246
392
|
const { config, credentials } = await loadConfigOrFail();
|
|
247
393
|
|
|
248
|
-
|
|
394
|
+
if (!config.previewKey) {
|
|
395
|
+
p.log.warn("No preview attached. Run tiendu preview list or tiendu preview create.");
|
|
396
|
+
process.exit(0);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const result = await fetchPreview(
|
|
249
400
|
config.apiBaseUrl,
|
|
250
401
|
credentials.apiKey,
|
|
251
402
|
config.storeId,
|
|
403
|
+
config.previewKey,
|
|
252
404
|
);
|
|
253
405
|
|
|
254
406
|
if (!result.ok) {
|
|
255
|
-
p.log.
|
|
407
|
+
p.log.warn(`Stored preview ${config.previewKey} was not found.`);
|
|
408
|
+
p.log.info("Run tiendu preview list to see available previews.");
|
|
256
409
|
process.exit(1);
|
|
257
410
|
}
|
|
258
411
|
|
|
259
|
-
const preview =
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
result.data.length === 0
|
|
263
|
-
? "No previews found for this store."
|
|
264
|
-
: "Run tiendu preview list to inspect available previews.",
|
|
265
|
-
);
|
|
266
|
-
process.exit(1);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const url = buildPreviewUrl(config.apiBaseUrl, preview.previewHostname);
|
|
412
|
+
const preview = result.data;
|
|
413
|
+
const url = getPreviewUrl(config.apiBaseUrl, preview);
|
|
414
|
+
const displayName = getPreviewDisplayName(preview);
|
|
270
415
|
|
|
271
416
|
p.note(
|
|
272
417
|
[
|
|
273
|
-
`Name: ${
|
|
418
|
+
`Name: ${displayName}`,
|
|
419
|
+
`Key: ${preview.previewKey}`,
|
|
274
420
|
`URL: ${url}`,
|
|
275
421
|
`Created: ${formatRelativeDate(preview.createdAt)}`,
|
|
276
422
|
].join("\n"),
|
|
277
|
-
"
|
|
423
|
+
"Attached preview",
|
|
278
424
|
);
|
|
279
425
|
};
|
|
280
426
|
|
|
281
|
-
export const
|
|
427
|
+
export const previewAttach = async (keyArg) => {
|
|
282
428
|
const { config, credentials } = await loadConfigOrFail();
|
|
283
429
|
|
|
284
|
-
|
|
430
|
+
if (!keyArg) {
|
|
431
|
+
await resolvePreviewKeyInteractively({ config, credentials });
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const spinner = p.spinner();
|
|
436
|
+
spinner.start("Validating preview...");
|
|
437
|
+
|
|
438
|
+
const result = await fetchPreview(
|
|
285
439
|
config.apiBaseUrl,
|
|
286
440
|
credentials.apiKey,
|
|
287
441
|
config.storeId,
|
|
442
|
+
keyArg,
|
|
288
443
|
);
|
|
289
|
-
|
|
290
|
-
|
|
444
|
+
|
|
445
|
+
if (!result.ok) {
|
|
446
|
+
spinner.stop("Preview not found.", 1);
|
|
447
|
+
p.log.error("Preview not found. Run tiendu preview list to see available previews.");
|
|
291
448
|
process.exit(1);
|
|
292
449
|
}
|
|
293
450
|
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
451
|
+
const preview = result.data;
|
|
452
|
+
const url = getPreviewUrl(config.apiBaseUrl, preview);
|
|
453
|
+
const displayName = getPreviewDisplayName(preview);
|
|
454
|
+
spinner.stop(`Attached to preview "${displayName}" (${preview.previewKey})`);
|
|
455
|
+
p.log.message(` ${url}`);
|
|
456
|
+
|
|
457
|
+
await writeConfig({ ...config, previewKey: preview.previewKey });
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
export const previewDetach = async () => {
|
|
461
|
+
const { config } = await loadConfigOrFail();
|
|
462
|
+
|
|
463
|
+
if (!config.previewKey) {
|
|
464
|
+
p.log.warn("No preview is currently attached.");
|
|
465
|
+
process.exit(0);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const detachedKey = config.previewKey;
|
|
469
|
+
const { previewKey: _, ...rest } = config;
|
|
470
|
+
await writeConfig(rest);
|
|
471
|
+
|
|
472
|
+
p.log.success(`Detached from preview ${detachedKey}. No active preview.`);
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
export const previewDelete = async (keyArg) => {
|
|
476
|
+
const { config, credentials } = await loadConfigOrFail();
|
|
477
|
+
|
|
478
|
+
let previewKey = keyArg;
|
|
479
|
+
|
|
480
|
+
if (!previewKey) {
|
|
481
|
+
if (!config.previewKey) {
|
|
482
|
+
p.log.warn("No preview attached and no key provided.");
|
|
483
|
+
p.log.info("Run tiendu preview delete <key> or tiendu preview attach first.");
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
previewKey = config.previewKey;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Fetch preview to show its name in the confirmation
|
|
490
|
+
const fetchResult = await fetchPreview(
|
|
491
|
+
config.apiBaseUrl,
|
|
492
|
+
credentials.apiKey,
|
|
493
|
+
config.storeId,
|
|
494
|
+
previewKey,
|
|
297
495
|
);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
? "No previews found for this store."
|
|
302
|
-
: "Could not determine the active preview. Run tiendu preview list first.",
|
|
303
|
-
);
|
|
496
|
+
|
|
497
|
+
if (!fetchResult.ok) {
|
|
498
|
+
p.log.error(`Preview ${previewKey} not found.`);
|
|
304
499
|
process.exit(1);
|
|
305
500
|
}
|
|
306
501
|
|
|
502
|
+
const displayName = getPreviewDisplayName(fetchResult.data);
|
|
503
|
+
const url = getPreviewUrl(config.apiBaseUrl, fetchResult.data);
|
|
504
|
+
|
|
307
505
|
const confirmed = await p.confirm({
|
|
308
|
-
message: `Delete preview ${
|
|
506
|
+
message: `Delete preview ${previewKey} "${displayName}" (${url})?`,
|
|
309
507
|
});
|
|
310
508
|
|
|
311
509
|
if (p.isCancel(confirmed) || !confirmed) {
|
|
@@ -320,7 +518,7 @@ export const previewDelete = async () => {
|
|
|
320
518
|
config.apiBaseUrl,
|
|
321
519
|
credentials.apiKey,
|
|
322
520
|
config.storeId,
|
|
323
|
-
|
|
521
|
+
previewKey,
|
|
324
522
|
);
|
|
325
523
|
|
|
326
524
|
if (!result.ok) {
|
|
@@ -331,48 +529,56 @@ export const previewDelete = async () => {
|
|
|
331
529
|
|
|
332
530
|
spinner.stop("Preview deleted.");
|
|
333
531
|
|
|
334
|
-
|
|
335
|
-
|
|
532
|
+
if (config.previewKey === previewKey) {
|
|
533
|
+
const { previewKey: _, ...rest } = config;
|
|
534
|
+
await writeConfig(rest);
|
|
535
|
+
}
|
|
336
536
|
};
|
|
337
537
|
|
|
338
538
|
export const previewOpen = async () => {
|
|
339
539
|
const { config, credentials } = await loadConfigOrFail();
|
|
340
540
|
|
|
541
|
+
if (!config.previewKey) {
|
|
542
|
+
p.log.warn("No preview attached. Run tiendu preview attach or tiendu preview create.");
|
|
543
|
+
process.exit(1);
|
|
544
|
+
}
|
|
545
|
+
|
|
341
546
|
const spinner = p.spinner();
|
|
342
547
|
spinner.start("Fetching preview URL...");
|
|
343
548
|
|
|
344
|
-
const result = await
|
|
549
|
+
const result = await fetchPreview(
|
|
345
550
|
config.apiBaseUrl,
|
|
346
551
|
credentials.apiKey,
|
|
347
552
|
config.storeId,
|
|
553
|
+
config.previewKey,
|
|
348
554
|
);
|
|
349
555
|
|
|
350
556
|
if (!result.ok) {
|
|
351
|
-
spinner.stop("
|
|
352
|
-
p.log.error(
|
|
353
|
-
process.exit(1);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const preview = resolveActivePreview(result.data, config.previewKey);
|
|
357
|
-
if (!preview) {
|
|
358
|
-
spinner.stop("Could not determine the active preview.", 1);
|
|
359
|
-
p.log.error(
|
|
360
|
-
result.data.length === 0
|
|
361
|
-
? "No previews found for this store."
|
|
362
|
-
: "Run tiendu preview list and then set or recreate the preview.",
|
|
363
|
-
);
|
|
557
|
+
spinner.stop("Preview not found.", 1);
|
|
558
|
+
p.log.error("Stored preview was not found. Run tiendu preview list.");
|
|
364
559
|
process.exit(1);
|
|
365
560
|
}
|
|
366
561
|
|
|
367
|
-
const
|
|
562
|
+
const preview = result.data;
|
|
563
|
+
const url = getPreviewUrl(config.apiBaseUrl, preview);
|
|
368
564
|
spinner.stop(`Opening ${url}`);
|
|
369
565
|
|
|
370
|
-
const {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
566
|
+
const { spawn } = await import("node:child_process");
|
|
567
|
+
|
|
568
|
+
if (process.platform === "win32") {
|
|
569
|
+
const child = spawn("cmd", ["/c", "start", "", url], {
|
|
570
|
+
detached: true,
|
|
571
|
+
stdio: "ignore",
|
|
572
|
+
windowsHide: true,
|
|
573
|
+
});
|
|
574
|
+
child.unref();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const cmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
579
|
+
const child = spawn(cmd, [url], {
|
|
580
|
+
detached: true,
|
|
581
|
+
stdio: "ignore",
|
|
582
|
+
});
|
|
583
|
+
child.unref();
|
|
378
584
|
};
|
package/lib/publish.mjs
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
|
-
import { loadConfigOrFail,
|
|
3
|
-
import {
|
|
2
|
+
import { loadConfigOrFail, isBuiltTheme } from "./config.mjs";
|
|
3
|
+
import {
|
|
4
|
+
fetchPreviewDetails,
|
|
5
|
+
publishPreview,
|
|
6
|
+
resolvePreviewKeyInteractively,
|
|
7
|
+
} from "./preview.mjs";
|
|
4
8
|
import { push } from "./push.mjs";
|
|
5
9
|
|
|
6
|
-
export const publish = async ({ skipBuild = false } = {}) => {
|
|
10
|
+
export const publish = async ({ skipBuild = false, previewKey: previewKeyArg } = {}) => {
|
|
7
11
|
const { config, credentials } = await loadConfigOrFail();
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// Resolve preview key: explicit arg > interactive picker
|
|
14
|
+
const previewKey = previewKeyArg ?? await resolvePreviewKeyInteractively({ config, credentials });
|
|
15
|
+
|
|
16
|
+
// Fetch preview to show its name in the confirmation
|
|
17
|
+
const fetchResult = await fetchPreviewDetails(
|
|
18
|
+
config.apiBaseUrl,
|
|
19
|
+
credentials.apiKey,
|
|
20
|
+
config.storeId,
|
|
21
|
+
previewKey,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const displayName = fetchResult.ok
|
|
25
|
+
? fetchResult.data.displayName
|
|
26
|
+
: previewKey;
|
|
27
|
+
const previewUrl = fetchResult.ok ? fetchResult.data.url : null;
|
|
13
28
|
|
|
14
29
|
const confirmed = await p.confirm({
|
|
15
|
-
message:
|
|
30
|
+
message: previewUrl
|
|
31
|
+
? `Publish preview "${displayName}" (${previewKey}) at ${previewUrl} to the live storefront?`
|
|
32
|
+
: `Publish preview "${displayName}" (${previewKey}) to the live storefront?`,
|
|
16
33
|
});
|
|
17
34
|
|
|
18
35
|
if (p.isCancel(confirmed) || !confirmed) {
|
|
@@ -26,17 +43,17 @@ export const publish = async ({ skipBuild = false } = {}) => {
|
|
|
26
43
|
? "Syncing existing dist/ output to the preview before publishing..."
|
|
27
44
|
: "Building and syncing the latest dist/ output before publishing...",
|
|
28
45
|
);
|
|
29
|
-
await push({ skipBuild });
|
|
46
|
+
await push({ skipBuild, previewKey });
|
|
30
47
|
}
|
|
31
48
|
|
|
32
49
|
const spinner = p.spinner();
|
|
33
|
-
spinner.start("Publishing preview...");
|
|
50
|
+
spinner.start("Publishing preview to live storefront...");
|
|
34
51
|
|
|
35
52
|
const result = await publishPreview(
|
|
36
53
|
config.apiBaseUrl,
|
|
37
54
|
credentials.apiKey,
|
|
38
55
|
config.storeId,
|
|
39
|
-
|
|
56
|
+
previewKey,
|
|
40
57
|
);
|
|
41
58
|
|
|
42
59
|
if (!result.ok) {
|
|
@@ -45,10 +62,8 @@ export const publish = async ({ skipBuild = false } = {}) => {
|
|
|
45
62
|
process.exit(1);
|
|
46
63
|
}
|
|
47
64
|
|
|
48
|
-
spinner.stop(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const { previewKey, ...rest } = config;
|
|
53
|
-
await writeConfig(rest);
|
|
65
|
+
spinner.stop(`Preview ${previewKey} published. Your live storefront has been updated.`);
|
|
66
|
+
if (previewUrl) {
|
|
67
|
+
p.log.message(` ${previewUrl}`);
|
|
68
|
+
}
|
|
54
69
|
};
|
package/lib/pull.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { loadConfigOrFail, isBuiltTheme, getDistDir } from "./config.mjs";
|
|
3
|
-
import { downloadStorefrontArchive } from "./api.mjs";
|
|
3
|
+
import { downloadStorefrontArchive, downloadPreviewArchive } from "./api.mjs";
|
|
4
|
+
import { fetchPreviewDetails } from "./preview.mjs";
|
|
4
5
|
import { extractZip } from "./zip.mjs";
|
|
5
6
|
|
|
6
7
|
/** @param {number} bytes */
|
|
@@ -10,32 +11,56 @@ const formatBytes = (bytes) => {
|
|
|
10
11
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
export const pull = async () => {
|
|
14
|
+
export const pull = async ({ previewKey } = {}) => {
|
|
14
15
|
const { config, credentials } = await loadConfigOrFail();
|
|
16
|
+
const previewDetails = previewKey
|
|
17
|
+
? await fetchPreviewDetails(
|
|
18
|
+
config.apiBaseUrl,
|
|
19
|
+
credentials.apiKey,
|
|
20
|
+
config.storeId,
|
|
21
|
+
previewKey,
|
|
22
|
+
)
|
|
23
|
+
: null;
|
|
15
24
|
|
|
16
25
|
const spinner = p.spinner();
|
|
17
|
-
|
|
26
|
+
const isPreviewPull = Boolean(previewKey);
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
spinner.start(
|
|
29
|
+
isPreviewPull
|
|
30
|
+
? `Downloading preview ${previewKey} from store #${config.storeId}...`
|
|
31
|
+
: `Downloading live theme from store #${config.storeId}...`,
|
|
23
32
|
);
|
|
24
33
|
|
|
34
|
+
const result = isPreviewPull
|
|
35
|
+
? await downloadPreviewArchive(
|
|
36
|
+
config.apiBaseUrl,
|
|
37
|
+
credentials.apiKey,
|
|
38
|
+
config.storeId,
|
|
39
|
+
previewKey,
|
|
40
|
+
)
|
|
41
|
+
: await downloadStorefrontArchive(
|
|
42
|
+
config.apiBaseUrl,
|
|
43
|
+
credentials.apiKey,
|
|
44
|
+
config.storeId,
|
|
45
|
+
);
|
|
46
|
+
|
|
25
47
|
if (!result.ok) {
|
|
26
48
|
spinner.stop("Download failed.", 1);
|
|
27
49
|
p.log.error(result.error);
|
|
28
50
|
process.exit(1);
|
|
29
51
|
}
|
|
30
52
|
|
|
31
|
-
spinner.
|
|
32
|
-
`Archive received (${formatBytes(result.data.length)}). Extracting...`,
|
|
33
|
-
);
|
|
53
|
+
spinner.message(`Extracting archive (${formatBytes(result.data.length)})...`);
|
|
34
54
|
|
|
35
55
|
const outputDir = (await isBuiltTheme()) ? getDistDir() : process.cwd();
|
|
36
56
|
const extractedFiles = await extractZip(result.data, outputDir);
|
|
37
57
|
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
const suffix = isPreviewPull ? ` from preview ${previewKey}` : "";
|
|
59
|
+
spinner.stop(
|
|
60
|
+
`${extractedFiles.length} file${extractedFiles.length === 1 ? "" : "s"} extracted${suffix}.`,
|
|
40
61
|
);
|
|
62
|
+
|
|
63
|
+
if (previewDetails?.ok) {
|
|
64
|
+
p.log.message(` ${previewDetails.data.url}`);
|
|
65
|
+
}
|
|
41
66
|
};
|