@binaryandthread/cli 0.1.9 → 0.2.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 +12 -3
- package/dist/bin.js +321 -98
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-RGXH3EZ6.js → chunk-AP5ARNWU.js} +124 -64
- package/dist/chunk-AP5ARNWU.js.map +1 -0
- package/dist/{interactive-2YROZBIN.js → interactive-4XWSUOOK.js} +2 -2
- package/package.json +4 -1
- package/dist/chunk-RGXH3EZ6.js.map +0 -1
- /package/dist/{interactive-2YROZBIN.js.map → interactive-4XWSUOOK.js.map} +0 -0
|
@@ -286,25 +286,45 @@ var StoreClient = class {
|
|
|
286
286
|
return this.regionsCache;
|
|
287
287
|
}
|
|
288
288
|
/**
|
|
289
|
-
* Resolve a region by country code,
|
|
290
|
-
*
|
|
289
|
+
* Resolve a region by country code, WITH provenance (matched / home / requested), so callers on
|
|
290
|
+
* the money path can fail closed on an unmatched explicit country instead of silently pricing in
|
|
291
|
+
* the wrong currency (F-A2). Region #1 is the launch/home default — SEK today, derived, never a
|
|
292
|
+
* hardcoded literal.
|
|
293
|
+
*
|
|
294
|
+
* Semantics:
|
|
295
|
+
* - country given AND a region ships there → { matched: true, home: false }.
|
|
296
|
+
* - country given, none ships there → { matched: false, home: false } (money surfaces MUST
|
|
297
|
+
* refuse; discovery may show the home region + a warning). We still return a region so a
|
|
298
|
+
* discovery command can render *something*, but `matched:false` is the signal to fail closed.
|
|
299
|
+
* - no country given → { matched: false, home: true } (the legitimate
|
|
300
|
+
* launch default — not a "wrong" currency).
|
|
291
301
|
*/
|
|
292
|
-
async
|
|
302
|
+
async resolveRegionResult(countryCode) {
|
|
293
303
|
const regions = this.regionsCache ?? await this.getRegions();
|
|
294
304
|
if (regions.length === 0) {
|
|
295
305
|
throw new Error("no regions configured \u2014 cannot resolve a region for pricing");
|
|
296
306
|
}
|
|
307
|
+
const home = regions[0];
|
|
297
308
|
if (countryCode) {
|
|
298
309
|
const cc = countryCode.toLowerCase();
|
|
299
310
|
const match = regions.find(
|
|
300
311
|
(r) => (r.countries ?? []).some((c) => (c.iso_2 ?? "").toLowerCase() === cc)
|
|
301
312
|
);
|
|
302
313
|
if (match) {
|
|
303
|
-
return match;
|
|
314
|
+
return { region: match, matched: true, requested: cc, home: false };
|
|
304
315
|
}
|
|
305
|
-
this.logWarn("store.region.unmatched", { countryCode: cc, fallback:
|
|
316
|
+
this.logWarn("store.region.unmatched", { countryCode: cc, fallback: home.id });
|
|
317
|
+
return { region: home, matched: false, requested: cc, home: false };
|
|
306
318
|
}
|
|
307
|
-
return
|
|
319
|
+
return { region: home, matched: false, requested: void 0, home: true };
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Resolve a region by country code, falling back to region #1. A thin wrapper over
|
|
323
|
+
* resolveRegionResult for callers that only need the region (back-compat).
|
|
324
|
+
* SEK is region #1 by configuration — NEVER a hardcoded literal.
|
|
325
|
+
*/
|
|
326
|
+
async resolveRegion(countryCode) {
|
|
327
|
+
return (await this.resolveRegionResult(countryCode)).region;
|
|
308
328
|
}
|
|
309
329
|
// ----- products (region resolved BEFORE pricing) ---------------------------
|
|
310
330
|
async listProducts(opts) {
|
|
@@ -658,6 +678,9 @@ function productLore(p) {
|
|
|
658
678
|
).map((s) => ({ url: s.url, label: s.label }));
|
|
659
679
|
return { text, sources };
|
|
660
680
|
}
|
|
681
|
+
function isCartTaxInclusive(cart) {
|
|
682
|
+
return (cart?.items ?? []).some((i) => i.is_tax_inclusive === true);
|
|
683
|
+
}
|
|
661
684
|
|
|
662
685
|
// src/voice.ts
|
|
663
686
|
var GLYPH = {
|
|
@@ -806,12 +829,15 @@ var BOX = {
|
|
|
806
829
|
ml: "\u251C",
|
|
807
830
|
mr: "\u2524"
|
|
808
831
|
};
|
|
832
|
+
function cursor(style) {
|
|
833
|
+
return cyan(style, "_");
|
|
834
|
+
}
|
|
809
835
|
function header(style, title, width = 56) {
|
|
810
836
|
const label = ` ${title} `;
|
|
811
837
|
const lead = BOX.h.repeat(2);
|
|
812
838
|
const used = lead.length + label.length;
|
|
813
|
-
const tail = BOX.h.repeat(Math.max(2, width - used));
|
|
814
|
-
return `${lead}${cyan(style, label)}${tail}`;
|
|
839
|
+
const tail = BOX.h.repeat(Math.max(2, width - used - 2));
|
|
840
|
+
return `${lead}${cyan(style, label)}${tail} ${cursor(style)}`;
|
|
815
841
|
}
|
|
816
842
|
function rule(width = 56) {
|
|
817
843
|
return BOX.h.repeat(width);
|
|
@@ -860,6 +886,20 @@ function colourMarker(colours) {
|
|
|
860
886
|
}
|
|
861
887
|
|
|
862
888
|
// src/commands/shared.ts
|
|
889
|
+
function regionLine(rr, style) {
|
|
890
|
+
const cc = (rr.requested ?? rr.region.countries?.[0]?.iso_2 ?? "").toLowerCase();
|
|
891
|
+
const loc = cc ? `${cc} \xB7 ` : "";
|
|
892
|
+
const nudge = rr.home ? " \xB7 not you? \u2192 bt region" : "";
|
|
893
|
+
return dim(style, `${GLYPH.gear} region: ${loc}${rr.region.currency_code}${nudge}`);
|
|
894
|
+
}
|
|
895
|
+
var SIZE_ORDER = ["xxs", "xs", "s", "m", "l", "xl", "2xl", "xxl", "3xl", "xxxl", "4xl", "5xl"];
|
|
896
|
+
function sortSizes(sizes) {
|
|
897
|
+
const rank = (s) => {
|
|
898
|
+
const i = SIZE_ORDER.indexOf(s.trim().toLowerCase());
|
|
899
|
+
return i === -1 ? SIZE_ORDER.length : i;
|
|
900
|
+
};
|
|
901
|
+
return [...sizes].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
902
|
+
}
|
|
863
903
|
function brandOf(args) {
|
|
864
904
|
return args?.brand ?? PLAIN;
|
|
865
905
|
}
|
|
@@ -870,11 +910,15 @@ function fmtPrice(amount, currency) {
|
|
|
870
910
|
const cur = (currency ?? "").toUpperCase();
|
|
871
911
|
return cur ? `${amount} ${cur}` : `${amount}`;
|
|
872
912
|
}
|
|
913
|
+
function taxSuffix(isTaxInclusive) {
|
|
914
|
+
return isTaxInclusive ? " incl. VAT" : "";
|
|
915
|
+
}
|
|
873
916
|
function firstPrice(p) {
|
|
874
917
|
const v = (p.variants ?? [])[0];
|
|
875
918
|
return {
|
|
876
919
|
amount: v?.calculated_price?.calculated_amount ?? null,
|
|
877
|
-
currency: v?.calculated_price?.currency_code ?? null
|
|
920
|
+
currency: v?.calculated_price?.currency_code ?? null,
|
|
921
|
+
taxInclusive: v?.calculated_price?.is_calculated_price_tax_inclusive ?? null
|
|
878
922
|
};
|
|
879
923
|
}
|
|
880
924
|
function stageMarker(channelStage) {
|
|
@@ -987,12 +1031,32 @@ function resolveOutput(program) {
|
|
|
987
1031
|
const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: opts.color === false });
|
|
988
1032
|
return { json, brand };
|
|
989
1033
|
}
|
|
1034
|
+
function failResult(spec) {
|
|
1035
|
+
const numericCode = typeof spec.code === "number" && spec.code >= 1 && spec.code <= 255;
|
|
1036
|
+
const exitCode = spec.exit ?? (numericCode ? spec.code : EXIT.GENERIC);
|
|
1037
|
+
return {
|
|
1038
|
+
lines: [fail(spec)],
|
|
1039
|
+
exitCode,
|
|
1040
|
+
json: {
|
|
1041
|
+
error: {
|
|
1042
|
+
code: exitCode,
|
|
1043
|
+
message: spec.what,
|
|
1044
|
+
...spec.fix ? { fix: spec.fix } : {},
|
|
1045
|
+
...spec.link ? { link: spec.link } : {}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
990
1050
|
function printResult(res, json) {
|
|
991
1051
|
if (res.exitCode) {
|
|
992
1052
|
process.exitCode = res.exitCode;
|
|
993
1053
|
}
|
|
994
|
-
if (json
|
|
995
|
-
|
|
1054
|
+
if (json) {
|
|
1055
|
+
if (res.json !== void 0) {
|
|
1056
|
+
process.stdout.write(JSON.stringify(res.json, null, 2) + "\n");
|
|
1057
|
+
} else {
|
|
1058
|
+
process.stderr.write(res.lines.join("\n") + "\n");
|
|
1059
|
+
}
|
|
996
1060
|
return;
|
|
997
1061
|
}
|
|
998
1062
|
process.stdout.write(res.lines.join("\n") + "\n");
|
|
@@ -1030,6 +1094,14 @@ ${urlFloor(blobUrl)}`;
|
|
|
1030
1094
|
}
|
|
1031
1095
|
var IMAGE_FETCH_TIMEOUT_MS = 5e3;
|
|
1032
1096
|
var IMAGE_BYTE_CAP = 2 * 1024 * 1024;
|
|
1097
|
+
function isWebp(buf) {
|
|
1098
|
+
return buf.length >= 12 && buf.toString("ascii", 0, 4) === "RIFF" && buf.toString("ascii", 8, 12) === "WEBP";
|
|
1099
|
+
}
|
|
1100
|
+
async function toPng(buf) {
|
|
1101
|
+
if (!isWebp(buf)) return buf;
|
|
1102
|
+
const sharp = (await import("sharp")).default;
|
|
1103
|
+
return sharp(buf).png().toBuffer();
|
|
1104
|
+
}
|
|
1033
1105
|
async function defaultRender(url, opts) {
|
|
1034
1106
|
const mod = await import("terminal-image");
|
|
1035
1107
|
const terminalImage = mod.default ?? mod;
|
|
@@ -1053,10 +1125,11 @@ async function defaultRender(url, opts) {
|
|
|
1053
1125
|
if (Number.isFinite(declaredLen) && declaredLen > cap) {
|
|
1054
1126
|
throw new Error("blob exceeds byte cap (declared)");
|
|
1055
1127
|
}
|
|
1056
|
-
const
|
|
1057
|
-
if (
|
|
1128
|
+
const rawBuf = Buffer.from(await res.arrayBuffer());
|
|
1129
|
+
if (rawBuf.byteLength === 0 || rawBuf.byteLength > cap) {
|
|
1058
1130
|
throw new Error("blob empty or exceeds byte cap");
|
|
1059
1131
|
}
|
|
1132
|
+
const buf = await toPng(rawBuf);
|
|
1060
1133
|
return bufferFn(buf, {
|
|
1061
1134
|
height: opts.height ?? DEFAULT_RENDER_HEIGHT,
|
|
1062
1135
|
preserveAspectRatio: true,
|
|
@@ -1067,30 +1140,21 @@ async function defaultRender(url, opts) {
|
|
|
1067
1140
|
// src/commands/view.ts
|
|
1068
1141
|
async function runView(client, config, args) {
|
|
1069
1142
|
const style = brandOf(args);
|
|
1070
|
-
const
|
|
1143
|
+
const rr = await client.resolveRegionResult(config.region);
|
|
1144
|
+
const region = rr.region;
|
|
1071
1145
|
const picked = await resolveProduct(client, region, args.handle);
|
|
1072
1146
|
const product = picked.product;
|
|
1073
1147
|
if (!product) {
|
|
1074
1148
|
if (picked.candidates && picked.candidates.length > 1) {
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
...picked.candidates.map((p) => ` ${p.handle ?? p.id}`),
|
|
1079
|
-
" or be exact \u2192 bt view <slug>"
|
|
1080
|
-
],
|
|
1081
|
-
exitCode: EXIT.DATAERR
|
|
1082
|
-
};
|
|
1149
|
+
const r = failResult({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${picked.candidates.length} designs \u2014 pick one:` });
|
|
1150
|
+
r.lines.push(...picked.candidates.map((p) => ` ${p.handle ?? p.id}`), " or be exact \u2192 bt view <slug>");
|
|
1151
|
+
return r;
|
|
1083
1152
|
}
|
|
1084
|
-
return {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
fix: "bt browse (to see what's in stock)"
|
|
1090
|
-
})
|
|
1091
|
-
],
|
|
1092
|
-
exitCode: EXIT.UNAVAILABLE
|
|
1093
|
-
};
|
|
1153
|
+
return failResult({
|
|
1154
|
+
code: EXIT.UNAVAILABLE,
|
|
1155
|
+
what: `no design here called "${args.handle}".`,
|
|
1156
|
+
fix: "bt browse (to see what's in stock)"
|
|
1157
|
+
});
|
|
1094
1158
|
}
|
|
1095
1159
|
const { title, handle } = displayName(product);
|
|
1096
1160
|
const colours = productColours(product);
|
|
@@ -1111,18 +1175,19 @@ async function runView(client, config, args) {
|
|
|
1111
1175
|
if (colours.length > 0) {
|
|
1112
1176
|
lines.push(` colours ${colours.map((c) => `${colourSwatch(c)} ${c}`).join(" \xB7 ")}`);
|
|
1113
1177
|
for (const c of colours) {
|
|
1114
|
-
const sizes = sizesForColour(product, c);
|
|
1178
|
+
const sizes = sortSizes(sizesForColour(product, c));
|
|
1115
1179
|
lines.push(` ${colourSwatch(c)} ${c.padEnd(6)} ${sizes.join(" ")}`);
|
|
1116
1180
|
}
|
|
1117
1181
|
} else {
|
|
1118
|
-
const sizes = (product.variants ?? []).map((v) => v.title ?? "?");
|
|
1182
|
+
const sizes = sortSizes((product.variants ?? []).map((v) => v.title ?? "?"));
|
|
1119
1183
|
lines.push(` sizes ${sizes.join(" ")}`);
|
|
1120
1184
|
}
|
|
1121
1185
|
const v0 = (product.variants ?? [])[0];
|
|
1122
1186
|
if (v0?.calculated_price) {
|
|
1123
1187
|
lines.push(
|
|
1124
|
-
` price ${cyan(style, fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code))}
|
|
1188
|
+
` price ${cyan(style, fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code))}${taxSuffix(v0.calculated_price.is_calculated_price_tax_inclusive)}`
|
|
1125
1189
|
);
|
|
1190
|
+
lines.push(` ${regionLine(rr, style)}`);
|
|
1126
1191
|
}
|
|
1127
1192
|
let imageUrl = product.thumbnail ?? null;
|
|
1128
1193
|
let blackNote = null;
|
|
@@ -1186,27 +1251,24 @@ function registerView(program, makeClient, loadConfig) {
|
|
|
1186
1251
|
// src/commands/stage.ts
|
|
1187
1252
|
async function runStage(client, config, args) {
|
|
1188
1253
|
const style = brandOf(args);
|
|
1189
|
-
const
|
|
1254
|
+
const rr = await client.resolveRegionResult(config.region);
|
|
1255
|
+
if (rr.requested && !rr.matched) {
|
|
1256
|
+
return failResult({
|
|
1257
|
+
code: EXIT.DATAERR,
|
|
1258
|
+
what: `we don't ship to ${rr.requested} yet.`,
|
|
1259
|
+
money: "nothing staged, nothing charged.",
|
|
1260
|
+
fix: "bt region --list (see where we ship, then bt region <cc>)"
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
const region = rr.region;
|
|
1190
1264
|
const resolved = await resolveProduct(client, region, args.handle);
|
|
1191
1265
|
if (!resolved.product) {
|
|
1192
1266
|
if (resolved.candidates && resolved.candidates.length > 1) {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
...resolved.candidates.map((p) => ` ${p.handle ?? p.id}`),
|
|
1197
|
-
" or be exact \u2192 bt stage <slug>"
|
|
1198
|
-
],
|
|
1199
|
-
exitCode: EXIT.DATAERR
|
|
1200
|
-
// ambiguous input → non-zero so `bt stage x && bt checkout` halts (d10)
|
|
1201
|
-
};
|
|
1267
|
+
const r = failResult({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${resolved.candidates.length} designs \u2014 pick one:` });
|
|
1268
|
+
r.lines.push(...resolved.candidates.map((p) => ` ${p.handle ?? p.id}`), " or be exact \u2192 bt stage <slug>");
|
|
1269
|
+
return r;
|
|
1202
1270
|
}
|
|
1203
|
-
return {
|
|
1204
|
-
lines: [
|
|
1205
|
-
fail({ code: EXIT.UNAVAILABLE, what: `no design here called "${args.handle}".`, fix: "bt browse" })
|
|
1206
|
-
],
|
|
1207
|
-
exitCode: EXIT.UNAVAILABLE
|
|
1208
|
-
// not-found → non-zero (d10)
|
|
1209
|
-
};
|
|
1271
|
+
return failResult({ code: EXIT.UNAVAILABLE, what: `no design here called "${args.handle}".`, fix: "bt browse" });
|
|
1210
1272
|
}
|
|
1211
1273
|
const product = resolved.product;
|
|
1212
1274
|
const matchNote = resolved.fuzzy && (product.handle ?? "") !== args.handle.trim() ? working(`matched "${product.handle}" from "${args.handle}"`) : null;
|
|
@@ -1216,17 +1278,11 @@ async function runStage(client, config, args) {
|
|
|
1216
1278
|
const variant = findVariant(product, { size, colour });
|
|
1217
1279
|
if (!variant) {
|
|
1218
1280
|
const have = colours.length ? ` available colours: ${colours.join(", ")}.` : "";
|
|
1219
|
-
return {
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
fix: `bt view ${args.handle} (to see available sizes + colours)${have}`
|
|
1225
|
-
})
|
|
1226
|
-
],
|
|
1227
|
-
exitCode: EXIT.DATAERR
|
|
1228
|
-
// unprocessable size+colour combo → non-zero (d10)
|
|
1229
|
-
};
|
|
1281
|
+
return failResult({
|
|
1282
|
+
code: EXIT.DATAERR,
|
|
1283
|
+
what: `no variant for ${args.handle}${size ? `#size=${size}` : ""}${colour ? ` (${colour})` : ""}.`,
|
|
1284
|
+
fix: `bt view ${args.handle} (to see available sizes + colours)${have}`
|
|
1285
|
+
});
|
|
1230
1286
|
}
|
|
1231
1287
|
if (args.dryRun) {
|
|
1232
1288
|
const colourSuffix2 = colour ? ` (${colour})` : "";
|
|
@@ -1325,6 +1381,7 @@ export {
|
|
|
1325
1381
|
sizesForColour,
|
|
1326
1382
|
findVariant,
|
|
1327
1383
|
productLore,
|
|
1384
|
+
isCartTaxInclusive,
|
|
1328
1385
|
GLYPH,
|
|
1329
1386
|
ok,
|
|
1330
1387
|
warn,
|
|
@@ -1343,8 +1400,10 @@ export {
|
|
|
1343
1400
|
contributionBar,
|
|
1344
1401
|
colourSwatch,
|
|
1345
1402
|
colourMarker,
|
|
1403
|
+
regionLine,
|
|
1346
1404
|
brandOf,
|
|
1347
1405
|
fmtPrice,
|
|
1406
|
+
taxSuffix,
|
|
1348
1407
|
firstPrice,
|
|
1349
1408
|
stageMarker,
|
|
1350
1409
|
displayName,
|
|
@@ -1353,10 +1412,11 @@ export {
|
|
|
1353
1412
|
resolveLineItemRef,
|
|
1354
1413
|
resolveProduct,
|
|
1355
1414
|
resolveOutput,
|
|
1415
|
+
failResult,
|
|
1356
1416
|
printResult,
|
|
1357
1417
|
runView,
|
|
1358
1418
|
registerView,
|
|
1359
1419
|
runStage,
|
|
1360
1420
|
registerStage
|
|
1361
1421
|
};
|
|
1362
|
-
//# sourceMappingURL=chunk-
|
|
1422
|
+
//# sourceMappingURL=chunk-AP5ARNWU.js.map
|