@binaryandthread/cli 0.1.9 → 0.2.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 +12 -3
- package/dist/bin.js +321 -98
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-RGXH3EZ6.js → chunk-NJ3DW5AX.js} +113 -62
- package/dist/chunk-NJ3DW5AX.js.map +1 -0
- package/dist/{interactive-2YROZBIN.js → interactive-5PBJCENZ.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-RGXH3EZ6.js.map +0 -1
- /package/dist/{interactive-2YROZBIN.js.map → interactive-5PBJCENZ.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");
|
|
@@ -1067,30 +1131,21 @@ async function defaultRender(url, opts) {
|
|
|
1067
1131
|
// src/commands/view.ts
|
|
1068
1132
|
async function runView(client, config, args) {
|
|
1069
1133
|
const style = brandOf(args);
|
|
1070
|
-
const
|
|
1134
|
+
const rr = await client.resolveRegionResult(config.region);
|
|
1135
|
+
const region = rr.region;
|
|
1071
1136
|
const picked = await resolveProduct(client, region, args.handle);
|
|
1072
1137
|
const product = picked.product;
|
|
1073
1138
|
if (!product) {
|
|
1074
1139
|
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
|
-
};
|
|
1140
|
+
const r = failResult({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${picked.candidates.length} designs \u2014 pick one:` });
|
|
1141
|
+
r.lines.push(...picked.candidates.map((p) => ` ${p.handle ?? p.id}`), " or be exact \u2192 bt view <slug>");
|
|
1142
|
+
return r;
|
|
1083
1143
|
}
|
|
1084
|
-
return {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
fix: "bt browse (to see what's in stock)"
|
|
1090
|
-
})
|
|
1091
|
-
],
|
|
1092
|
-
exitCode: EXIT.UNAVAILABLE
|
|
1093
|
-
};
|
|
1144
|
+
return failResult({
|
|
1145
|
+
code: EXIT.UNAVAILABLE,
|
|
1146
|
+
what: `no design here called "${args.handle}".`,
|
|
1147
|
+
fix: "bt browse (to see what's in stock)"
|
|
1148
|
+
});
|
|
1094
1149
|
}
|
|
1095
1150
|
const { title, handle } = displayName(product);
|
|
1096
1151
|
const colours = productColours(product);
|
|
@@ -1111,18 +1166,19 @@ async function runView(client, config, args) {
|
|
|
1111
1166
|
if (colours.length > 0) {
|
|
1112
1167
|
lines.push(` colours ${colours.map((c) => `${colourSwatch(c)} ${c}`).join(" \xB7 ")}`);
|
|
1113
1168
|
for (const c of colours) {
|
|
1114
|
-
const sizes = sizesForColour(product, c);
|
|
1169
|
+
const sizes = sortSizes(sizesForColour(product, c));
|
|
1115
1170
|
lines.push(` ${colourSwatch(c)} ${c.padEnd(6)} ${sizes.join(" ")}`);
|
|
1116
1171
|
}
|
|
1117
1172
|
} else {
|
|
1118
|
-
const sizes = (product.variants ?? []).map((v) => v.title ?? "?");
|
|
1173
|
+
const sizes = sortSizes((product.variants ?? []).map((v) => v.title ?? "?"));
|
|
1119
1174
|
lines.push(` sizes ${sizes.join(" ")}`);
|
|
1120
1175
|
}
|
|
1121
1176
|
const v0 = (product.variants ?? [])[0];
|
|
1122
1177
|
if (v0?.calculated_price) {
|
|
1123
1178
|
lines.push(
|
|
1124
|
-
` price ${cyan(style, fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code))}
|
|
1179
|
+
` price ${cyan(style, fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code))}${taxSuffix(v0.calculated_price.is_calculated_price_tax_inclusive)}`
|
|
1125
1180
|
);
|
|
1181
|
+
lines.push(` ${regionLine(rr, style)}`);
|
|
1126
1182
|
}
|
|
1127
1183
|
let imageUrl = product.thumbnail ?? null;
|
|
1128
1184
|
let blackNote = null;
|
|
@@ -1186,27 +1242,24 @@ function registerView(program, makeClient, loadConfig) {
|
|
|
1186
1242
|
// src/commands/stage.ts
|
|
1187
1243
|
async function runStage(client, config, args) {
|
|
1188
1244
|
const style = brandOf(args);
|
|
1189
|
-
const
|
|
1245
|
+
const rr = await client.resolveRegionResult(config.region);
|
|
1246
|
+
if (rr.requested && !rr.matched) {
|
|
1247
|
+
return failResult({
|
|
1248
|
+
code: EXIT.DATAERR,
|
|
1249
|
+
what: `we don't ship to ${rr.requested} yet.`,
|
|
1250
|
+
money: "nothing staged, nothing charged.",
|
|
1251
|
+
fix: "bt region --list (see where we ship, then bt region <cc>)"
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
const region = rr.region;
|
|
1190
1255
|
const resolved = await resolveProduct(client, region, args.handle);
|
|
1191
1256
|
if (!resolved.product) {
|
|
1192
1257
|
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
|
-
};
|
|
1258
|
+
const r = failResult({ code: EXIT.DATAERR, what: `"${args.handle}" matches ${resolved.candidates.length} designs \u2014 pick one:` });
|
|
1259
|
+
r.lines.push(...resolved.candidates.map((p) => ` ${p.handle ?? p.id}`), " or be exact \u2192 bt stage <slug>");
|
|
1260
|
+
return r;
|
|
1202
1261
|
}
|
|
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
|
-
};
|
|
1262
|
+
return failResult({ code: EXIT.UNAVAILABLE, what: `no design here called "${args.handle}".`, fix: "bt browse" });
|
|
1210
1263
|
}
|
|
1211
1264
|
const product = resolved.product;
|
|
1212
1265
|
const matchNote = resolved.fuzzy && (product.handle ?? "") !== args.handle.trim() ? working(`matched "${product.handle}" from "${args.handle}"`) : null;
|
|
@@ -1216,17 +1269,11 @@ async function runStage(client, config, args) {
|
|
|
1216
1269
|
const variant = findVariant(product, { size, colour });
|
|
1217
1270
|
if (!variant) {
|
|
1218
1271
|
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
|
-
};
|
|
1272
|
+
return failResult({
|
|
1273
|
+
code: EXIT.DATAERR,
|
|
1274
|
+
what: `no variant for ${args.handle}${size ? `#size=${size}` : ""}${colour ? ` (${colour})` : ""}.`,
|
|
1275
|
+
fix: `bt view ${args.handle} (to see available sizes + colours)${have}`
|
|
1276
|
+
});
|
|
1230
1277
|
}
|
|
1231
1278
|
if (args.dryRun) {
|
|
1232
1279
|
const colourSuffix2 = colour ? ` (${colour})` : "";
|
|
@@ -1325,6 +1372,7 @@ export {
|
|
|
1325
1372
|
sizesForColour,
|
|
1326
1373
|
findVariant,
|
|
1327
1374
|
productLore,
|
|
1375
|
+
isCartTaxInclusive,
|
|
1328
1376
|
GLYPH,
|
|
1329
1377
|
ok,
|
|
1330
1378
|
warn,
|
|
@@ -1343,8 +1391,10 @@ export {
|
|
|
1343
1391
|
contributionBar,
|
|
1344
1392
|
colourSwatch,
|
|
1345
1393
|
colourMarker,
|
|
1394
|
+
regionLine,
|
|
1346
1395
|
brandOf,
|
|
1347
1396
|
fmtPrice,
|
|
1397
|
+
taxSuffix,
|
|
1348
1398
|
firstPrice,
|
|
1349
1399
|
stageMarker,
|
|
1350
1400
|
displayName,
|
|
@@ -1353,10 +1403,11 @@ export {
|
|
|
1353
1403
|
resolveLineItemRef,
|
|
1354
1404
|
resolveProduct,
|
|
1355
1405
|
resolveOutput,
|
|
1406
|
+
failResult,
|
|
1356
1407
|
printResult,
|
|
1357
1408
|
runView,
|
|
1358
1409
|
registerView,
|
|
1359
1410
|
runStage,
|
|
1360
1411
|
registerStage
|
|
1361
1412
|
};
|
|
1362
|
-
//# sourceMappingURL=chunk-
|
|
1413
|
+
//# sourceMappingURL=chunk-NJ3DW5AX.js.map
|