@binaryandthread/cli 0.3.2 → 0.4.1
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 +4 -4
- package/dist/bin.js +38 -25
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-BFMH7VCE.js → chunk-42NDSW24.js} +344 -73
- package/dist/chunk-42NDSW24.js.map +1 -0
- package/dist/{chunk-RGFMXWD5.js → chunk-T7MFWTB7.js} +2 -1
- package/dist/chunk-T7MFWTB7.js.map +1 -0
- package/dist/{config-WV4DTQXU.js → config-L4X7G563.js} +2 -2
- package/dist/{interactive-BOS6VMRJ.js → interactive-CVHHGMCQ.js} +29 -11
- package/dist/interactive-CVHHGMCQ.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-BFMH7VCE.js.map +0 -1
- package/dist/chunk-RGFMXWD5.js.map +0 -1
- package/dist/interactive-BOS6VMRJ.js.map +0 -1
- /package/dist/{config-WV4DTQXU.js.map → config-L4X7G563.js.map} +0 -0
|
@@ -545,6 +545,154 @@ function isAllowedImageHost(url) {
|
|
|
545
545
|
(allowed) => host === allowed || host.endsWith("." + allowed)
|
|
546
546
|
);
|
|
547
547
|
}
|
|
548
|
+
var FITS = ["regular", "oversize"];
|
|
549
|
+
function parseFit(v) {
|
|
550
|
+
const s = typeof v === "string" ? v.trim().toLowerCase() : "";
|
|
551
|
+
return s === "regular" || s === "oversize" ? s : null;
|
|
552
|
+
}
|
|
553
|
+
function fitLabel(f) {
|
|
554
|
+
return f === "oversize" ? "Oversize" : "Regular";
|
|
555
|
+
}
|
|
556
|
+
function variantFit(v) {
|
|
557
|
+
const opt = (v.options ?? []).find((o) => (o.option?.title ?? "").toLowerCase() === "fit");
|
|
558
|
+
return opt ? parseFit(opt.value) : null;
|
|
559
|
+
}
|
|
560
|
+
function hasFitAxis(p) {
|
|
561
|
+
return (p.variants ?? []).some((v) => variantFit(v) !== null);
|
|
562
|
+
}
|
|
563
|
+
function productFits(p) {
|
|
564
|
+
const seen = /* @__PURE__ */ new Set();
|
|
565
|
+
for (const v of p.variants ?? []) {
|
|
566
|
+
const f = variantFit(v);
|
|
567
|
+
if (f) seen.add(f);
|
|
568
|
+
}
|
|
569
|
+
return FITS.filter((f) => seen.has(f));
|
|
570
|
+
}
|
|
571
|
+
var BLANK_COLOURS = {
|
|
572
|
+
"gildan-5000": ["black", "white"],
|
|
573
|
+
"sols-03996": ["black", "white"],
|
|
574
|
+
"shaka-shmhss": ["black", "white", "khaki"]
|
|
575
|
+
};
|
|
576
|
+
function blankFor(fit, countryCode) {
|
|
577
|
+
if (fit === "regular") return "gildan-5000";
|
|
578
|
+
return (countryCode ?? "").trim().toLowerCase() === "us" ? "shaka-shmhss" : "sols-03996";
|
|
579
|
+
}
|
|
580
|
+
var BLANK_SIZES = {
|
|
581
|
+
"gildan-5000": ["s", "m", "l", "xl", "2xl", "3xl", "4xl", "5xl"],
|
|
582
|
+
"shaka-shmhss": ["s", "m", "l", "xl", "2xl", "3xl", "4xl", "5xl"],
|
|
583
|
+
"sols-03996": ["xs", "s", "m", "l", "xl", "2xl", "3xl"]
|
|
584
|
+
};
|
|
585
|
+
function sizeKey(size) {
|
|
586
|
+
const s = size.trim().toLowerCase();
|
|
587
|
+
return s === "xxl" ? "2xl" : s === "xxxl" ? "3xl" : s;
|
|
588
|
+
}
|
|
589
|
+
function colourBuyable(fit, colour, countryCode) {
|
|
590
|
+
return BLANK_COLOURS[blankFor(fit, countryCode)].includes(colour.trim().toLowerCase());
|
|
591
|
+
}
|
|
592
|
+
function sizeBuyable(fit, size, countryCode) {
|
|
593
|
+
return BLANK_SIZES[blankFor(fit, countryCode)].includes(sizeKey(size));
|
|
594
|
+
}
|
|
595
|
+
function legacyFit(p) {
|
|
596
|
+
const md = p.metadata ?? {};
|
|
597
|
+
const hay = [typeof md.fit === "string" ? md.fit : "", p.title ?? "", p.handle ?? ""].join(" ");
|
|
598
|
+
return /oversiz|drop.?shoulder/i.test(hay) ? "oversize" : "regular";
|
|
599
|
+
}
|
|
600
|
+
function gateFit(p, fit) {
|
|
601
|
+
if (fit) return fit;
|
|
602
|
+
return hasFitAxis(p) ? null : legacyFit(p);
|
|
603
|
+
}
|
|
604
|
+
function variantsOfFit(p, fit) {
|
|
605
|
+
const all = p.variants ?? [];
|
|
606
|
+
return fit === null ? all : all.filter((v) => variantFit(v) === fit);
|
|
607
|
+
}
|
|
608
|
+
function coloursFor(p, fit, countryCode) {
|
|
609
|
+
const gate = gateFit(p, fit);
|
|
610
|
+
const out = [];
|
|
611
|
+
for (const v of variantsOfFit(p, fit)) {
|
|
612
|
+
const c = variantColour(v);
|
|
613
|
+
if (!c || out.includes(c)) continue;
|
|
614
|
+
if (gate && countryCode !== void 0 && !colourBuyable(gate, c, countryCode)) continue;
|
|
615
|
+
out.push(c);
|
|
616
|
+
}
|
|
617
|
+
return out;
|
|
618
|
+
}
|
|
619
|
+
function sizesFor(p, fit, colour, countryCode) {
|
|
620
|
+
const gate = gateFit(p, fit);
|
|
621
|
+
const target = colour.trim().toLowerCase();
|
|
622
|
+
const out = [];
|
|
623
|
+
for (const v of variantsOfFit(p, fit)) {
|
|
624
|
+
if (variantColour(v) !== target) continue;
|
|
625
|
+
const s = sizeOfVariant(v);
|
|
626
|
+
if (!s || out.includes(s)) continue;
|
|
627
|
+
if (gate && countryCode !== void 0 && !sizeBuyable(gate, s, countryCode)) continue;
|
|
628
|
+
out.push(s);
|
|
629
|
+
}
|
|
630
|
+
return out;
|
|
631
|
+
}
|
|
632
|
+
function cheapest(vs) {
|
|
633
|
+
const priced = vs.filter((v2) => typeof v2.calculated_price?.calculated_amount === "number").sort(
|
|
634
|
+
(a, b) => a.calculated_price.calculated_amount - b.calculated_price.calculated_amount
|
|
635
|
+
);
|
|
636
|
+
const v = priced[0];
|
|
637
|
+
return {
|
|
638
|
+
amount: v?.calculated_price?.calculated_amount ?? null,
|
|
639
|
+
currency: v?.calculated_price?.currency_code ?? null,
|
|
640
|
+
taxInclusive: v?.calculated_price?.is_calculated_price_tax_inclusive ?? null
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
function priceFrom(p) {
|
|
644
|
+
const fits = productFits(p);
|
|
645
|
+
const base = cheapest(p.variants ?? []);
|
|
646
|
+
const amounts = new Set(fits.map((f) => cheapest(variantsOfFit(p, f)).amount));
|
|
647
|
+
return { ...base, from: fits.length > 1 && amounts.size > 1 };
|
|
648
|
+
}
|
|
649
|
+
function priceFor(p, fit) {
|
|
650
|
+
return cheapest(variantsOfFit(p, fit));
|
|
651
|
+
}
|
|
652
|
+
function resolveVariant(p, opts) {
|
|
653
|
+
const fits = productFits(p);
|
|
654
|
+
const requested = parseFit(opts.fit);
|
|
655
|
+
const askedFor = typeof opts.fit === "string" ? opts.fit.trim() : "";
|
|
656
|
+
let fit = null;
|
|
657
|
+
if (fits.length > 0) {
|
|
658
|
+
if (requested && fits.includes(requested)) fit = requested;
|
|
659
|
+
else if (askedFor) return { ok: false, reason: "fit_not_offered", requested: askedFor, fits };
|
|
660
|
+
else if (fits.length === 1) fit = fits[0];
|
|
661
|
+
else return { ok: false, reason: "ambiguous_fit", fits: fits.map((f) => ({ fit: f, ...priceFor(p, f) })) };
|
|
662
|
+
}
|
|
663
|
+
const pool = variantsOfFit(p, fit);
|
|
664
|
+
const gate = gateFit(p, fit);
|
|
665
|
+
const colours = coloursFor(p, fit, opts.countryCode);
|
|
666
|
+
const colour = (opts.colour ?? "").trim().toLowerCase() || (colours[0] ?? null);
|
|
667
|
+
if (gate && colour && opts.countryCode !== void 0 && !colourBuyable(gate, colour, opts.countryCode)) {
|
|
668
|
+
return { ok: false, reason: "fit_unavailable", fit: gate, colour };
|
|
669
|
+
}
|
|
670
|
+
const size = (opts.size ?? "").trim() || null;
|
|
671
|
+
const sizeLc = size?.toLowerCase();
|
|
672
|
+
const buyableSizes = colour ? sizesFor(p, fit, colour, opts.countryCode) : [];
|
|
673
|
+
const sizeExists = !!size && pool.some((x) => (sizeOfVariant(x) ?? x.title ?? "").toLowerCase() === size.toLowerCase());
|
|
674
|
+
if (gate && size && sizeExists && opts.countryCode !== void 0 && !sizeBuyable(gate, size, opts.countryCode)) {
|
|
675
|
+
return { ok: false, reason: "size_unavailable", fit: gate, size, sizes: buyableSizes };
|
|
676
|
+
}
|
|
677
|
+
const sellable = gate && opts.countryCode !== void 0 ? pool.filter((x) => {
|
|
678
|
+
const s = sizeOfVariant(x);
|
|
679
|
+
return !s || sizeBuyable(gate, s, opts.countryCode);
|
|
680
|
+
}) : pool;
|
|
681
|
+
const v = sellable.find((x) => {
|
|
682
|
+
const colourOk = !colour || variantColour(x) === colour;
|
|
683
|
+
const sizeOk = !sizeLc || (sizeOfVariant(x) ?? "").toLowerCase() === sizeLc || (x.title ?? "").toLowerCase() === sizeLc;
|
|
684
|
+
return colourOk && sizeOk;
|
|
685
|
+
});
|
|
686
|
+
if (!v) {
|
|
687
|
+
return { ok: false, reason: "not_found", fit, colour, size, colours, sizes: buyableSizes };
|
|
688
|
+
}
|
|
689
|
+
return { ok: true, variant: v, fit, colour: variantColour(v), size: sizeOfVariant(v) };
|
|
690
|
+
}
|
|
691
|
+
var SIZE_TOKEN = /^(xxs|xs|s|m|l|xl|xxl|2xl|3xl|xxxl|4xl|5xl)$/i;
|
|
692
|
+
var FIT_TOKEN = /^(regular|oversize)$/i;
|
|
693
|
+
function titleSegments(title) {
|
|
694
|
+
return (title ?? "").split("/").map((s) => s.trim()).filter(Boolean);
|
|
695
|
+
}
|
|
548
696
|
function displayColour(colour) {
|
|
549
697
|
const c = colour.trim();
|
|
550
698
|
if (!c) return c;
|
|
@@ -557,11 +705,10 @@ function variantColour(v) {
|
|
|
557
705
|
if (opt && typeof opt.value === "string" && opt.value.trim()) {
|
|
558
706
|
return opt.value.trim().toLowerCase();
|
|
559
707
|
}
|
|
560
|
-
const
|
|
561
|
-
if (
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
if (tail) return tail.toLowerCase();
|
|
708
|
+
const segs = titleSegments(v.title);
|
|
709
|
+
if (segs.length >= 2) {
|
|
710
|
+
const seg = segs.find((s) => !SIZE_TOKEN.test(s) && !FIT_TOKEN.test(s));
|
|
711
|
+
if (seg) return seg.toLowerCase();
|
|
565
712
|
}
|
|
566
713
|
return null;
|
|
567
714
|
}
|
|
@@ -575,32 +722,28 @@ function productColours(p) {
|
|
|
575
722
|
}
|
|
576
723
|
return seen;
|
|
577
724
|
}
|
|
578
|
-
function sizesForColour(p, colour) {
|
|
579
|
-
const target = colour.trim().toLowerCase();
|
|
580
|
-
const sizes = [];
|
|
581
|
-
for (const v of p.variants ?? []) {
|
|
582
|
-
if (variantColour(v) !== target) continue;
|
|
583
|
-
const size = sizeOfVariant(v);
|
|
584
|
-
if (size && !sizes.includes(size)) {
|
|
585
|
-
sizes.push(size);
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return sizes;
|
|
589
|
-
}
|
|
590
725
|
function sizeOfVariant(v) {
|
|
591
726
|
const opt = (v.options ?? []).find((o) => (o.option?.title ?? "").toLowerCase() === "size");
|
|
592
727
|
if (opt && typeof opt.value === "string" && opt.value.trim()) {
|
|
593
728
|
return opt.value.trim();
|
|
594
729
|
}
|
|
595
|
-
const
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
}
|
|
600
|
-
return title || null;
|
|
730
|
+
const segs = titleSegments(v.title);
|
|
731
|
+
const token = segs.find((s) => SIZE_TOKEN.test(s));
|
|
732
|
+
if (token) return token;
|
|
733
|
+
return segs.length === 1 ? segs[0] : segs[0] ?? null;
|
|
601
734
|
}
|
|
602
735
|
function findVariant(p, opts = {}) {
|
|
603
|
-
const
|
|
736
|
+
const fits = productFits(p);
|
|
737
|
+
let fit = null;
|
|
738
|
+
if (fits.length > 0) {
|
|
739
|
+
const wanted = parseFit(opts.fit);
|
|
740
|
+
const askedFor = typeof opts.fit === "string" ? opts.fit.trim() : "";
|
|
741
|
+
if (wanted && fits.includes(wanted)) fit = wanted;
|
|
742
|
+
else if (askedFor) return void 0;
|
|
743
|
+
else if (fits.length === 1) fit = fits[0];
|
|
744
|
+
else return void 0;
|
|
745
|
+
}
|
|
746
|
+
const variants = (p.variants ?? []).filter((v) => fit === null || variantFit(v) === fit);
|
|
604
747
|
const size = opts.size?.trim().toLowerCase();
|
|
605
748
|
const colour = opts.colour?.trim().toLowerCase();
|
|
606
749
|
const sizeMatches = (v) => {
|
|
@@ -660,6 +803,24 @@ function imageForColour(p, colour) {
|
|
|
660
803
|
}
|
|
661
804
|
return null;
|
|
662
805
|
}
|
|
806
|
+
function imageFor(p, sel) {
|
|
807
|
+
const colour = sel.colour.trim().toLowerCase();
|
|
808
|
+
if (!colour) return null;
|
|
809
|
+
const metas = (p.images ?? []).map((img) => ({
|
|
810
|
+
url: (img?.url ?? "").trim(),
|
|
811
|
+
scene: typeof img?.metadata?.["scene"] === "string" ? String(img.metadata["scene"]) : null,
|
|
812
|
+
fit: parseFit(img?.metadata?.["fit"]),
|
|
813
|
+
colour: typeof img?.metadata?.["colour"] === "string" ? String(img.metadata["colour"]).toLowerCase() : null
|
|
814
|
+
}));
|
|
815
|
+
const tagged = metas.filter((m) => m.url && m.colour === colour && m.scene === PREFERRED_SCENE);
|
|
816
|
+
const exact = tagged.find((m) => sel.fit !== null && m.fit === sel.fit);
|
|
817
|
+
if (exact) return exact.url;
|
|
818
|
+
const neutral = tagged.find((m) => m.fit === null);
|
|
819
|
+
if (neutral) return neutral.url;
|
|
820
|
+
const otherFitOnly = tagged.length > 0 && sel.fit !== null;
|
|
821
|
+
if (otherFitOnly) return null;
|
|
822
|
+
return imageForColour(p, colour);
|
|
823
|
+
}
|
|
663
824
|
function firstImageUrl(images) {
|
|
664
825
|
for (const img of images ?? []) {
|
|
665
826
|
const u = (img?.url ?? "").trim();
|
|
@@ -673,15 +834,19 @@ function escapeRegex(s) {
|
|
|
673
834
|
function str(v, fallback) {
|
|
674
835
|
return typeof v === "string" && v.length > 0 ? v : fallback;
|
|
675
836
|
}
|
|
676
|
-
function productSpecs(p) {
|
|
837
|
+
function productSpecs(p, fit) {
|
|
677
838
|
const md = p.metadata ?? {};
|
|
839
|
+
const byFit = md.specs_by_fit ?? null;
|
|
840
|
+
const fitKey = fit ?? (byFit && Object.keys(byFit).length === 1 ? Object.keys(byFit)[0] : void 0);
|
|
841
|
+
const src = fitKey && byFit?.[fitKey] || md;
|
|
842
|
+
const pick = (k, fallback) => str(src[k] ?? md[k], fallback);
|
|
678
843
|
return [
|
|
679
|
-
{ key: "material", value:
|
|
680
|
-
{ key: "weight", value:
|
|
681
|
-
{ key: "fit", value:
|
|
682
|
-
{ key: "print", value:
|
|
683
|
-
{ key: "delivery", value:
|
|
684
|
-
{ key: "care", value:
|
|
844
|
+
{ key: "material", value: pick("material", "100% cotton") },
|
|
845
|
+
{ key: "weight", value: pick("weight", "180 g/m\xB2") },
|
|
846
|
+
{ key: "fit", value: pick("fit", "Unisex heavyweight crewneck") },
|
|
847
|
+
{ key: "print", value: pick("print", "Front print") },
|
|
848
|
+
{ key: "delivery", value: pick("delivery", "Ships in 2\u20135 business days + transit") },
|
|
849
|
+
{ key: "care", value: pick("care", "Cold wash, line dry") }
|
|
685
850
|
];
|
|
686
851
|
}
|
|
687
852
|
function productLore(p) {
|
|
@@ -891,6 +1056,7 @@ function colourSwatch(colour) {
|
|
|
891
1056
|
const c = colour.trim().toLowerCase();
|
|
892
1057
|
if (c === "white") return "\u2B1C";
|
|
893
1058
|
if (c === "black") return "\u2B1B";
|
|
1059
|
+
if (c === "khaki") return "\u{1F7EB}";
|
|
894
1060
|
return "\u25FC";
|
|
895
1061
|
}
|
|
896
1062
|
function colourMarker(colours) {
|
|
@@ -928,13 +1094,8 @@ function fmtPrice(amount, currency) {
|
|
|
928
1094
|
function taxSuffix(isTaxInclusive) {
|
|
929
1095
|
return isTaxInclusive ? " incl. VAT" : "";
|
|
930
1096
|
}
|
|
931
|
-
function
|
|
932
|
-
|
|
933
|
-
return {
|
|
934
|
-
amount: v?.calculated_price?.calculated_amount ?? null,
|
|
935
|
-
currency: v?.calculated_price?.currency_code ?? null,
|
|
936
|
-
taxInclusive: v?.calculated_price?.is_calculated_price_tax_inclusive ?? null
|
|
937
|
-
};
|
|
1097
|
+
function priceLine(p) {
|
|
1098
|
+
return priceFrom(p);
|
|
938
1099
|
}
|
|
939
1100
|
function stageMarker(channelStage) {
|
|
940
1101
|
return channelStage === "next" ? "\u25CB on next" : "\u25CF in stock";
|
|
@@ -944,7 +1105,14 @@ function displayName(p) {
|
|
|
944
1105
|
const title = (p.title ?? "").toString().trim() || handle;
|
|
945
1106
|
return { title, handle };
|
|
946
1107
|
}
|
|
1108
|
+
function lineOption(item, titles) {
|
|
1109
|
+
const o = item.variant?.options?.find((x) => titles.includes((x.option?.title ?? "").toLowerCase()));
|
|
1110
|
+
const v = typeof o?.value === "string" ? o.value.trim() : "";
|
|
1111
|
+
return v ? v : null;
|
|
1112
|
+
}
|
|
947
1113
|
function lineColour(item) {
|
|
1114
|
+
const opt = lineOption(item, ["colour", "color"]);
|
|
1115
|
+
if (opt) return opt.toLowerCase();
|
|
948
1116
|
const vt = (item.variant_title ?? "").toString();
|
|
949
1117
|
if (vt) {
|
|
950
1118
|
const c = variantColour({ id: "x", title: vt });
|
|
@@ -952,6 +1120,15 @@ function lineColour(item) {
|
|
|
952
1120
|
}
|
|
953
1121
|
return null;
|
|
954
1122
|
}
|
|
1123
|
+
function lineFit(item) {
|
|
1124
|
+
const opt = parseFit(lineOption(item, ["fit"]));
|
|
1125
|
+
if (opt) return opt;
|
|
1126
|
+
for (const seg of (item.variant_title ?? "").toString().split("/")) {
|
|
1127
|
+
const f = parseFit(seg);
|
|
1128
|
+
if (f) return f;
|
|
1129
|
+
}
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
955
1132
|
function shortItemId(id) {
|
|
956
1133
|
const s = (id ?? "").toString();
|
|
957
1134
|
return s.length <= 6 ? s : s.slice(-6);
|
|
@@ -962,7 +1139,9 @@ function formatCartLine(item, index, currency, brand) {
|
|
|
962
1139
|
const title = (item.title ?? item.product_title ?? "item").toString();
|
|
963
1140
|
const idTag = item.id ? dimTag(style, ` (#${shortItemId(item.id)})`) : "";
|
|
964
1141
|
const colour = lineColour(item);
|
|
965
|
-
const
|
|
1142
|
+
const fit = lineFit(item);
|
|
1143
|
+
const tags = [colour, fit ? fitLabel(fit).toLowerCase() : null].filter(Boolean).join(", ");
|
|
1144
|
+
const colourTag = tags ? ` \xB7 ${tags}` : "";
|
|
966
1145
|
const price = item.total != null ? ` ${accentPrice(style, fmtPrice(item.total, currency))}` : "";
|
|
967
1146
|
return ` ${ref} ${title}${colourTag}${idTag} \xB7 qty ${item.quantity}${price}`;
|
|
968
1147
|
}
|
|
@@ -1032,6 +1211,17 @@ async function resolveProduct(client, region, selector) {
|
|
|
1032
1211
|
if (exact) {
|
|
1033
1212
|
return { product: exact };
|
|
1034
1213
|
}
|
|
1214
|
+
const m = needle.match(/^(.+)-oversize$/i);
|
|
1215
|
+
if (m && m[1]) {
|
|
1216
|
+
const base = await client.getProduct(m[1], { region });
|
|
1217
|
+
if (base) {
|
|
1218
|
+
return {
|
|
1219
|
+
product: base,
|
|
1220
|
+
impliedFit: "oversize",
|
|
1221
|
+
aliasNote: `"${needle}" is a retired handle \xB7 resolved to ${base.handle ?? m[1]} --fit oversize`
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1035
1225
|
}
|
|
1036
1226
|
const products = await client.listProducts({ region });
|
|
1037
1227
|
const r = resolveProductSelector(products, needle);
|
|
@@ -1172,47 +1362,70 @@ async function runView(client, config, args) {
|
|
|
1172
1362
|
});
|
|
1173
1363
|
}
|
|
1174
1364
|
const { title, handle } = displayName(product);
|
|
1175
|
-
const
|
|
1365
|
+
const fits = productFits(product);
|
|
1366
|
+
const requestedFit = parseFit(args.fit ?? picked.impliedFit);
|
|
1367
|
+
if (args.fit && (!requestedFit || !fits.includes(requestedFit))) {
|
|
1368
|
+
return failResult({
|
|
1369
|
+
code: EXIT.DATAERR,
|
|
1370
|
+
what: fits.length ? `no fit called "${args.fit}" for ${handle}.` : `${handle} comes in one fit; --fit does not apply.`,
|
|
1371
|
+
fix: fits.length ? `bt view ${handle} --fit ${fits.join(" | ")}` : `bt view ${handle}`
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
const fit = fits.length ? requestedFit ?? (fits.includes("regular") ? "regular" : fits[0]) : null;
|
|
1375
|
+
const countryCode = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
|
|
1376
|
+
const colours = coloursFor(product, fit, countryCode);
|
|
1377
|
+
const unavailable = coloursFor(product, fit).filter((c) => !colours.includes(c));
|
|
1176
1378
|
const lines = [];
|
|
1177
1379
|
if (picked.fuzzy && handle !== args.handle.trim()) {
|
|
1178
1380
|
lines.push(working(`matched "${handle}" from "${args.handle}"`));
|
|
1179
1381
|
}
|
|
1382
|
+
if (picked.aliasNote) {
|
|
1383
|
+
lines.push(working(picked.aliasNote));
|
|
1384
|
+
}
|
|
1180
1385
|
lines.push(header(style, title));
|
|
1181
1386
|
lines.push(` ${dim(style, handle)}`);
|
|
1182
1387
|
if (product.description) {
|
|
1183
1388
|
lines.push(empty(` ${product.description}`));
|
|
1184
1389
|
}
|
|
1185
1390
|
const fabric = product.metadata?.["fabric"] ?? null;
|
|
1186
|
-
|
|
1391
|
+
if (fit && fits.length > 1) {
|
|
1392
|
+
const other = fits.find((f) => f !== fit);
|
|
1393
|
+
const op = priceFor(product, other);
|
|
1394
|
+
lines.push(` fits ${fits.map((f) => fitLabel(f)).join(" \xB7 ")} \u2192 viewing ${fitLabel(fit)} \xB7 the other \u2192 bt view ${handle} --fit ${other} (${fmtPrice(op.amount, op.currency)})`);
|
|
1395
|
+
}
|
|
1396
|
+
const specs = productSpecs(product, fit);
|
|
1187
1397
|
for (const s of specs) {
|
|
1188
1398
|
lines.push(` ${s.key.padEnd(9)}${s.value}`);
|
|
1189
1399
|
}
|
|
1190
|
-
if (colours.length > 0) {
|
|
1400
|
+
if (colours.length > 0 || unavailable.length > 0) {
|
|
1191
1401
|
lines.push(` colours ${colours.map((c) => `${colourSwatch(c)} ${c}`).join(" \xB7 ")}`);
|
|
1192
1402
|
for (const c of colours) {
|
|
1193
|
-
const sizes = sortSizes(
|
|
1403
|
+
const sizes = sortSizes(sizesFor(product, fit, c, countryCode));
|
|
1194
1404
|
lines.push(` ${colourSwatch(c)} ${c.padEnd(6)} ${sizes.join(" ")}`);
|
|
1195
1405
|
}
|
|
1406
|
+
for (const c of unavailable) {
|
|
1407
|
+
lines.push(dim(style, ` ${colourSwatch(c)} ${c.padEnd(6)} not available where you ship (${countryCode ?? "your region"})`));
|
|
1408
|
+
}
|
|
1196
1409
|
} else {
|
|
1197
1410
|
const sizes = sortSizes((product.variants ?? []).map((v) => v.title ?? "?"));
|
|
1198
1411
|
lines.push(` sizes ${sizes.join(" ")}`);
|
|
1199
1412
|
}
|
|
1200
|
-
const
|
|
1201
|
-
if (
|
|
1413
|
+
const pr = fit ? { ...priceFor(product, fit), from: false } : priceLine(product);
|
|
1414
|
+
if (pr.amount != null) {
|
|
1202
1415
|
lines.push(
|
|
1203
|
-
` price ${cyan(style, fmtPrice(
|
|
1416
|
+
` price ${cyan(style, `${pr.from ? "from " : ""}${fmtPrice(pr.amount, pr.currency)}`)}${taxSuffix(pr.taxInclusive)}`
|
|
1204
1417
|
);
|
|
1205
1418
|
lines.push(` ${regionLine(rr, style, config.region_source)}`);
|
|
1206
1419
|
}
|
|
1207
1420
|
let imageUrl = product.thumbnail ?? null;
|
|
1208
1421
|
let blackNote = null;
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
const resolved = imageForColour(product, wanted);
|
|
1422
|
+
const wanted = args.colour?.trim().toLowerCase() || (fit ? colours[0] : void 0);
|
|
1423
|
+
if (wanted) {
|
|
1424
|
+
const resolved = imageFor(product, { fit, colour: wanted }) ?? (fit === null ? imageForColour(product, wanted) : null);
|
|
1212
1425
|
if (resolved) {
|
|
1213
1426
|
imageUrl = resolved;
|
|
1214
|
-
} else {
|
|
1215
|
-
blackNote = ` ${displayColour(wanted)} available \u2014 showing the default mockup (no ${wanted} render on file).`;
|
|
1427
|
+
} else if (args.colour) {
|
|
1428
|
+
blackNote = unavailable.includes(wanted) ? ` ${displayColour(wanted)} isn't available where you ship \u2014 showing the default mockup.` : ` ${displayColour(wanted)} available \u2014 showing the default mockup (no ${wanted} render on file).`;
|
|
1216
1429
|
}
|
|
1217
1430
|
}
|
|
1218
1431
|
if (imageUrl) {
|
|
@@ -1224,7 +1437,7 @@ async function runView(client, config, args) {
|
|
|
1224
1437
|
}
|
|
1225
1438
|
if (colours.length > 1 && !args.colour) {
|
|
1226
1439
|
const others = colours.filter((c) => c !== colours[0]).join(", ");
|
|
1227
|
-
lines.push(` see another colour \u2192 bt view ${handle} --colour ${others.split(",")[0]?.trim()}`);
|
|
1440
|
+
lines.push(` see another colour \u2192 bt view ${handle} --colour ${others.split(",")[0]?.trim()}${fit && fits.length > 1 ? ` --fit ${fit}` : ""}`);
|
|
1228
1441
|
}
|
|
1229
1442
|
if (productLore(product).text) {
|
|
1230
1443
|
lines.push(` the story \u2192 bt blame ${handle}`);
|
|
@@ -1237,11 +1450,17 @@ async function runView(client, config, args) {
|
|
|
1237
1450
|
title: product.title,
|
|
1238
1451
|
fabric,
|
|
1239
1452
|
specs,
|
|
1453
|
+
fits,
|
|
1454
|
+
fit,
|
|
1240
1455
|
colours,
|
|
1456
|
+
unavailable_colours: unavailable,
|
|
1241
1457
|
thumbnail: product.thumbnail,
|
|
1458
|
+
// CAT-31: `size` is the REAL size (never the variant title); fit/colour from the options.
|
|
1242
1459
|
variants: (product.variants ?? []).map((v) => ({
|
|
1243
1460
|
id: v.id,
|
|
1244
|
-
|
|
1461
|
+
fit: variantFit(v),
|
|
1462
|
+
colour: variantColour(v),
|
|
1463
|
+
size: sizeOfVariant(v),
|
|
1245
1464
|
price: v.calculated_price?.calculated_amount ?? null,
|
|
1246
1465
|
currency: v.calculated_price?.currency_code ?? null
|
|
1247
1466
|
}))
|
|
@@ -1249,12 +1468,13 @@ async function runView(client, config, args) {
|
|
|
1249
1468
|
};
|
|
1250
1469
|
}
|
|
1251
1470
|
function registerView(program, makeClient, loadConfig) {
|
|
1252
|
-
program.command("view").alias("show").argument("<handle>", "the design: a slug, a bt browse number (3), or a fuzzy hint (termnl)").option("--colour <colour>", "render a specific colour's mockup (e.g. black)").description("render a design: specs + image (always with the link as the floor).").action(async (handle, opts) => {
|
|
1471
|
+
program.command("view").alias("show").argument("<handle>", "the design: a slug, a bt browse number (3), or a fuzzy hint (termnl)").option("--colour <colour>", "render a specific colour's mockup (e.g. black)").option("--fit <fit>", "regular or oversize: which fit's sizes, price and image to show (default: regular)").description("render a design: specs + image (always with the link as the floor).").action(async (handle, opts) => {
|
|
1253
1472
|
const { json, brand } = resolveOutput(program);
|
|
1254
1473
|
const noColor = program.opts().color === false;
|
|
1255
1474
|
const res = await runView(makeClient(), loadConfig(), {
|
|
1256
1475
|
handle,
|
|
1257
1476
|
colour: opts.colour,
|
|
1477
|
+
fit: opts.fit,
|
|
1258
1478
|
json,
|
|
1259
1479
|
noColor,
|
|
1260
1480
|
brand
|
|
@@ -1279,38 +1499,82 @@ async function runStage(client, config, args) {
|
|
|
1279
1499
|
const resolved = await resolveProduct(client, region, args.handle);
|
|
1280
1500
|
if (!resolved.product) {
|
|
1281
1501
|
if (resolved.candidates && resolved.candidates.length > 1) {
|
|
1282
|
-
const
|
|
1283
|
-
|
|
1284
|
-
return
|
|
1502
|
+
const r2 = failResult({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${resolved.candidates.length} designs \u2014 pick one:` });
|
|
1503
|
+
r2.lines.push(...resolved.candidates.map((p) => ` ${p.handle ?? p.id}`), " or be exact \u2192 bt stage <slug>");
|
|
1504
|
+
return r2;
|
|
1285
1505
|
}
|
|
1286
1506
|
return failResult({ code: EXIT.UNAVAILABLE, what: `no design here called "${args.handle}".`, fix: "bt browse" });
|
|
1287
1507
|
}
|
|
1288
1508
|
const product = resolved.product;
|
|
1289
1509
|
const matchNote = resolved.fuzzy && (product.handle ?? "") !== args.handle.trim() ? working(`matched "${product.handle}" from "${args.handle}"`) : null;
|
|
1290
1510
|
const size = args.size ?? config.defaultSize;
|
|
1291
|
-
const
|
|
1292
|
-
const
|
|
1293
|
-
const
|
|
1294
|
-
|
|
1295
|
-
|
|
1511
|
+
const countryCode = (rr.requested ?? region.countries?.[0]?.iso_2 ?? "").toLowerCase() || null;
|
|
1512
|
+
const explicitFit = args.fit ?? resolved.impliedFit;
|
|
1513
|
+
const fitArg = explicitFit ?? (productFits(product).length > 1 ? config.defaultFit : void 0);
|
|
1514
|
+
const r = resolveVariant(product, { fit: fitArg, size, colour: args.colour ?? config.defaultColour, countryCode });
|
|
1515
|
+
const handleShown = product.handle ?? args.handle;
|
|
1516
|
+
if (!r.ok) {
|
|
1517
|
+
if (r.reason === "ambiguous_fit") {
|
|
1518
|
+
const fitList = r.fits.map((f) => `${f.fit} (${fmtPrice(f.amount, f.currency)})`).join(" \xB7 ");
|
|
1519
|
+
const res = failResult({
|
|
1520
|
+
code: EXIT.DATAERR,
|
|
1521
|
+
what: `${handleShown} comes in two fits: ${fitList}.`,
|
|
1522
|
+
money: "nothing staged, nothing charged.",
|
|
1523
|
+
fix: `bt stage ${handleShown} --fit ${r.fits[0].fit}${size ? ` --size ${size}` : ""}`
|
|
1524
|
+
});
|
|
1525
|
+
res.lines.push(` or set yours once \u2192 bt config set defaultFit ${r.fits[0].fit}`);
|
|
1526
|
+
return res;
|
|
1527
|
+
}
|
|
1528
|
+
if (r.reason === "fit_not_offered") {
|
|
1529
|
+
return failResult({
|
|
1530
|
+
code: EXIT.DATAERR,
|
|
1531
|
+
what: `${handleShown} isn't sold in "${r.requested}"${r.fits.length ? ` \u2014 it comes in ${r.fits.join(" \xB7 ")}` : ""}.`,
|
|
1532
|
+
money: "nothing staged, nothing charged.",
|
|
1533
|
+
fix: r.fits.length ? `bt stage ${handleShown} --fit ${r.fits[0]}${size ? ` --size ${size}` : ""}` : `bt stage ${handleShown}${size ? ` --size ${size}` : ""} (this design comes in one fit)`
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
if (r.reason === "fit_unavailable") {
|
|
1537
|
+
return failResult({
|
|
1538
|
+
code: EXIT.DATAERR,
|
|
1539
|
+
what: `${r.colour} is not available in ${r.fit} where you ship (${countryCode ?? "your region"}).`,
|
|
1540
|
+
money: "nothing staged, nothing charged.",
|
|
1541
|
+
fix: `bt view ${handleShown} --fit ${r.fit} (to see the colours you can buy)`
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
if (r.reason === "size_unavailable") {
|
|
1545
|
+
return failResult({
|
|
1546
|
+
code: EXIT.DATAERR,
|
|
1547
|
+
what: `${r.size} is not made in ${r.fit} where you ship (${countryCode ?? "your region"}).`,
|
|
1548
|
+
money: "nothing staged, nothing charged.",
|
|
1549
|
+
fix: r.sizes.length ? `bt stage ${handleShown} --fit ${r.fit} --size ${r.sizes[0]} (sizes here: ${r.sizes.join(" ")})` : `bt view ${handleShown} --fit ${r.fit} (to see the sizes you can buy)`
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
const have = r.colours.length ? ` available colours: ${r.colours.join(", ")}.` : "";
|
|
1553
|
+
const haveSizes = r.sizes.length ? ` sizes: ${r.sizes.join(" ")}.` : "";
|
|
1296
1554
|
return failResult({
|
|
1297
1555
|
code: EXIT.DATAERR,
|
|
1298
|
-
what: `no variant for ${args.handle}${size ? `#size=${size}` : ""}${colour ? ` (${colour})` : ""}.`,
|
|
1299
|
-
fix: `bt view ${args.handle} (to see available sizes + colours)${have}`
|
|
1556
|
+
what: `no variant for ${args.handle}${r.fit ? ` --fit ${r.fit}` : ""}${size ? `#size=${size}` : ""}${r.colour ? ` (${r.colour})` : ""}.`,
|
|
1557
|
+
fix: `bt view ${args.handle}${r.fit ? ` --fit ${r.fit}` : ""} (to see available sizes + colours)${have}${haveSizes}`
|
|
1300
1558
|
});
|
|
1301
1559
|
}
|
|
1560
|
+
const variant = r.variant;
|
|
1561
|
+
const colour = r.colour ?? void 0;
|
|
1562
|
+
const fit = r.fit;
|
|
1563
|
+
const refTags = [colour, fit].filter(Boolean).join(", ");
|
|
1564
|
+
const colourSuffix = refTags ? ` (${refTags})` : "";
|
|
1302
1565
|
if (args.dryRun) {
|
|
1303
|
-
const colourSuffix2 = colour ? ` (${colour})` : "";
|
|
1304
1566
|
return {
|
|
1305
1567
|
lines: [
|
|
1306
1568
|
...matchNote ? [matchNote] : [],
|
|
1307
|
-
|
|
1569
|
+
...resolved.aliasNote ? [warn(resolved.aliasNote)] : [],
|
|
1570
|
+
ok(`dry-run \u2014 would stage ${product.handle ?? args.handle}${size ? `#${size}` : ""}${colourSuffix} (nothing staged).`),
|
|
1308
1571
|
" run without --dry-run to acquire the inventory lock."
|
|
1309
1572
|
],
|
|
1310
1573
|
json: args.json ? {
|
|
1311
1574
|
dry_run: true,
|
|
1312
1575
|
would_stage: {
|
|
1313
1576
|
handle: product.handle ?? args.handle,
|
|
1577
|
+
fit: fit ?? null,
|
|
1314
1578
|
size: size ?? null,
|
|
1315
1579
|
colour: colour ?? null,
|
|
1316
1580
|
variant_id: variant.id
|
|
@@ -1322,6 +1586,9 @@ async function runStage(client, config, args) {
|
|
|
1322
1586
|
if (matchNote) {
|
|
1323
1587
|
lines.push(matchNote);
|
|
1324
1588
|
}
|
|
1589
|
+
if (resolved.aliasNote) {
|
|
1590
|
+
lines.push(warn(resolved.aliasNote));
|
|
1591
|
+
}
|
|
1325
1592
|
let cartId = config.cart_id;
|
|
1326
1593
|
let recovered = false;
|
|
1327
1594
|
if (!cartId) {
|
|
@@ -1348,7 +1615,6 @@ async function runStage(client, config, args) {
|
|
|
1348
1615
|
)
|
|
1349
1616
|
);
|
|
1350
1617
|
}
|
|
1351
|
-
const colourSuffix = colour ? ` (${colour})` : "";
|
|
1352
1618
|
lines.push(
|
|
1353
1619
|
ok(
|
|
1354
1620
|
`staged ${product.handle ?? args.handle}${size ? `#${size}` : ""}${colourSuffix} \xB7 inventory lock acquired (held 10m)`
|
|
@@ -1363,22 +1629,23 @@ async function runStage(client, config, args) {
|
|
|
1363
1629
|
// persist the cart_id atomically BEFORE returning (the router calls config.save)
|
|
1364
1630
|
save: { cart_id: cartId },
|
|
1365
1631
|
lines,
|
|
1366
|
-
json: args.json ? { cart_id: cartId, recovered, colour: colour ?? null, items: cart.items ?? [], total: cart.total ?? null } : void 0
|
|
1632
|
+
json: args.json ? { cart_id: cartId, recovered, fit: fit ?? null, colour: colour ?? null, items: cart.items ?? [], total: cart.total ?? null } : void 0
|
|
1367
1633
|
};
|
|
1368
1634
|
}
|
|
1369
1635
|
function registerStage(program, makeClient, loadConfig) {
|
|
1370
|
-
program.command("stage").alias("add").argument("<handle>", "the design: a slug (terminal-velocity), a bt browse number (3), or a fuzzy hint (termnl)").option("--size <size>", "garment size (default: your last commit's size)").option("--colour <colour>", "colour, e.g. white / black (default: the product's first colour)").option("--dry-run", "resolve the variant + show what would be staged, without staging").description("reserve stock \u2014 acquires an inventory lock, reserves nothing permanent.").action(async (handle, opts) => {
|
|
1636
|
+
program.command("stage").alias("add").argument("<handle>", "the design: a slug (terminal-velocity), a bt browse number (3), or a fuzzy hint (termnl)").option("--size <size>", "garment size (default: your last commit's size)").option("--colour <colour>", "colour, e.g. white / black (default: the product's first colour)").option("--fit <fit>", "regular or oversize (required when a design comes in both; default: your defaultFit)").option("--dry-run", "resolve the variant + show what would be staged, without staging").description("reserve stock \u2014 acquires an inventory lock, reserves nothing permanent.").action(async (handle, opts) => {
|
|
1371
1637
|
const { json, brand } = resolveOutput(program);
|
|
1372
1638
|
const res = await runStage(makeClient(), loadConfig(), {
|
|
1373
1639
|
handle,
|
|
1374
1640
|
size: opts.size,
|
|
1375
1641
|
colour: opts.colour,
|
|
1642
|
+
fit: opts.fit,
|
|
1376
1643
|
dryRun: opts.dryRun,
|
|
1377
1644
|
json,
|
|
1378
1645
|
brand
|
|
1379
1646
|
});
|
|
1380
1647
|
if (res.save) {
|
|
1381
|
-
const { saveConfig } = await import("./config-
|
|
1648
|
+
const { saveConfig } = await import("./config-L4X7G563.js");
|
|
1382
1649
|
saveConfig(res.save);
|
|
1383
1650
|
}
|
|
1384
1651
|
printResult(res, json);
|
|
@@ -1392,8 +1659,12 @@ export {
|
|
|
1392
1659
|
StoreClient,
|
|
1393
1660
|
BT_DEFAULT_STORE_URL,
|
|
1394
1661
|
BT_DEFAULT_PUBLISHABLE_KEY,
|
|
1662
|
+
fitLabel,
|
|
1663
|
+
productFits,
|
|
1664
|
+
coloursFor,
|
|
1665
|
+
sizesFor,
|
|
1666
|
+
priceFor,
|
|
1395
1667
|
productColours,
|
|
1396
|
-
sizesForColour,
|
|
1397
1668
|
findVariant,
|
|
1398
1669
|
productLore,
|
|
1399
1670
|
isCartTaxInclusive,
|
|
@@ -1419,7 +1690,7 @@ export {
|
|
|
1419
1690
|
brandOf,
|
|
1420
1691
|
fmtPrice,
|
|
1421
1692
|
taxSuffix,
|
|
1422
|
-
|
|
1693
|
+
priceLine,
|
|
1423
1694
|
stageMarker,
|
|
1424
1695
|
displayName,
|
|
1425
1696
|
lineColour,
|
|
@@ -1434,4 +1705,4 @@ export {
|
|
|
1434
1705
|
runStage,
|
|
1435
1706
|
registerStage
|
|
1436
1707
|
};
|
|
1437
|
-
//# sourceMappingURL=chunk-
|
|
1708
|
+
//# sourceMappingURL=chunk-42NDSW24.js.map
|