@binaryandthread/cli 0.1.6 → 0.1.8

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/dist/bin.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ configDir,
3
4
  configPath,
4
5
  forgetConfig,
5
6
  loadConfig,
@@ -48,7 +49,7 @@ import {
48
49
  versionLine,
49
50
  warn,
50
51
  working
51
- } from "./chunk-CA3GGJGF.js";
52
+ } from "./chunk-B6PV7EQG.js";
52
53
  import {
53
54
  __require
54
55
  } from "./chunk-7OCVIDC7.js";
@@ -77,7 +78,7 @@ function makeLogger(debug, write = (s) => process.stderr.write(s)) {
77
78
  }
78
79
 
79
80
  // src/version.ts
80
- var VERSION = "0.1.6";
81
+ var VERSION = "0.1.8";
81
82
 
82
83
  // src/eggs.ts
83
84
  function teapot() {
@@ -213,16 +214,99 @@ function isMainModule(argv1, moduleUrl, realpath = realpathSync) {
213
214
  }
214
215
  }
215
216
 
216
- // src/keychain.ts
217
+ // src/update-notice.ts
217
218
  import * as fs from "fs";
218
- import * as os from "os";
219
219
  import * as path from "path";
220
+ var CACHE_FILE = "update-check.json";
221
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
222
+ var REGISTRY_LATEST = "https://registry.npmjs.org/@binaryandthread/cli/latest";
223
+ var FETCH_TIMEOUT_MS = 2e3;
224
+ function parseSemver(v) {
225
+ const m = /^(\d+)\.(\d+)\.(\d+)/.exec((v ?? "").trim());
226
+ return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
227
+ }
228
+ function semverGt(a, b) {
229
+ const pa = parseSemver(a);
230
+ const pb = parseSemver(b);
231
+ if (!pa || !pb) return false;
232
+ for (let i = 0; i < 3; i++) {
233
+ if (pa[i] > pb[i]) return true;
234
+ if (pa[i] < pb[i]) return false;
235
+ }
236
+ return false;
237
+ }
238
+ function updateNoticeLine(current, latest) {
239
+ if (!latest || !semverGt(latest, current)) return null;
240
+ return `\u2699 a newer bt is out (${latest}). update \u2192 npm i -g @binaryandthread/cli@latest`;
241
+ }
242
+ function isUpdateCheckSuppressed(env, argv, stderrIsTTY) {
243
+ if (!stderrIsTTY) return true;
244
+ if (argv.includes("--json")) return true;
245
+ if (env.CI || env.BT_NO_UPDATE_NOTIFIER || env.NO_UPDATE_NOTIFIER) return true;
246
+ return false;
247
+ }
248
+ function maybeNotifyUpdate(deps) {
249
+ if (isUpdateCheckSuppressed(deps.env, deps.argv, deps.stderrIsTTY)) return;
250
+ const now = deps.now ?? Date.now();
251
+ const read = deps.readCache ?? readUpdateCache;
252
+ const refresh = deps.refresh ?? ((n) => void fetchLatestIntoCache(n));
253
+ const cache = read();
254
+ const line = updateNoticeLine(deps.current, cache?.latest);
255
+ if (line) deps.write(line + "\n");
256
+ if (!cache || now - cache.lastCheck > CHECK_INTERVAL_MS) {
257
+ refresh(now);
258
+ }
259
+ }
260
+ function cachePath() {
261
+ return path.join(configDir(), CACHE_FILE);
262
+ }
263
+ function readUpdateCache() {
264
+ try {
265
+ const j = JSON.parse(fs.readFileSync(cachePath(), "utf8"));
266
+ if (j && typeof j.latest === "string" && typeof j.lastCheck === "number") {
267
+ return { latest: j.latest, lastCheck: j.lastCheck };
268
+ }
269
+ } catch {
270
+ }
271
+ return null;
272
+ }
273
+ function writeUpdateCache(c) {
274
+ try {
275
+ fs.mkdirSync(configDir(), { recursive: true });
276
+ const tmp = cachePath() + ".tmp";
277
+ fs.writeFileSync(tmp, JSON.stringify(c), "utf8");
278
+ fs.renameSync(tmp, cachePath());
279
+ } catch {
280
+ }
281
+ }
282
+ async function fetchLatestIntoCache(now) {
283
+ try {
284
+ const ctrl = new AbortController();
285
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
286
+ try {
287
+ const res = await fetch(REGISTRY_LATEST, { signal: ctrl.signal });
288
+ if (!res.ok) return;
289
+ const data = await res.json();
290
+ if (typeof data.version === "string") {
291
+ writeUpdateCache({ lastCheck: now, latest: data.version });
292
+ }
293
+ } finally {
294
+ clearTimeout(timer);
295
+ }
296
+ } catch {
297
+ }
298
+ }
299
+
300
+ // src/keychain.ts
301
+ import * as fs2 from "fs";
302
+ import * as os from "os";
303
+ import * as path2 from "path";
220
304
  var KEYCHAIN_SERVICE = "binaryandthread/bt";
221
305
  var KEYCHAIN_ACCOUNT = "jwt";
222
306
  function fallbackTokenPath() {
223
307
  const xdg = process.env.XDG_CONFIG_HOME;
224
- const base = xdg && xdg.trim() ? xdg : path.join(os.homedir(), ".config");
225
- return path.join(base, "bt", "token");
308
+ const base = xdg && xdg.trim() ? xdg : path2.join(os.homedir(), ".config");
309
+ return path2.join(base, "bt", "token");
226
310
  }
227
311
  function defaultMakeEntry(service, account) {
228
312
  try {
@@ -239,7 +323,7 @@ function setToken(jwt, deps = {}) {
239
323
  try {
240
324
  entry.setPassword(jwt);
241
325
  try {
242
- fs.rmSync(fallbackTokenPath(), { force: true });
326
+ fs2.rmSync(fallbackTokenPath(), { force: true });
243
327
  } catch {
244
328
  }
245
329
  return;
@@ -247,10 +331,10 @@ function setToken(jwt, deps = {}) {
247
331
  }
248
332
  }
249
333
  const target = fallbackTokenPath();
250
- fs.mkdirSync(path.dirname(target), { recursive: true });
334
+ fs2.mkdirSync(path2.dirname(target), { recursive: true });
251
335
  const tmp = target + ".tmp";
252
- fs.writeFileSync(tmp, jwt, { encoding: "utf8", mode: 384 });
253
- fs.renameSync(tmp, target);
336
+ fs2.writeFileSync(tmp, jwt, { encoding: "utf8", mode: 384 });
337
+ fs2.renameSync(tmp, target);
254
338
  }
255
339
  function getToken(deps = {}) {
256
340
  const make = deps.makeEntry ?? defaultMakeEntry;
@@ -265,7 +349,7 @@ function getToken(deps = {}) {
265
349
  }
266
350
  }
267
351
  try {
268
- const raw = fs.readFileSync(fallbackTokenPath(), "utf8").trim();
352
+ const raw = fs2.readFileSync(fallbackTokenPath(), "utf8").trim();
269
353
  return raw || null;
270
354
  } catch {
271
355
  return null;
@@ -281,7 +365,7 @@ function deleteToken(deps = {}) {
281
365
  }
282
366
  }
283
367
  try {
284
- fs.rmSync(fallbackTokenPath(), { force: true });
368
+ fs2.rmSync(fallbackTokenPath(), { force: true });
285
369
  } catch {
286
370
  }
287
371
  }
@@ -356,7 +440,7 @@ function registerBrowse(program, makeClient2, loadConfig2) {
356
440
  program.command("browse").alias("ls").description("list what's in stock on a branch.").option("--branch <branch>", "main (live) or next (terminal-only drops)").option("--pick", "opt into the arrow-key picker (default is the numbered table \u2014 use bt stage <n>)").option("--plain", "force the static table (the default; kept for back-compat)").action(async (opts) => {
357
441
  const { json, brand } = resolveOutput(program);
358
442
  if (opts.pick && !json && brand.color && process.stdout.isTTY && process.stdin.isTTY) {
359
- const { runInteractiveBrowse } = await import("./interactive-HKGPS4PX.js");
443
+ const { runInteractiveBrowse } = await import("./interactive-BYDQJSG4.js");
360
444
  const handled = await runInteractiveBrowse(makeClient2(), loadConfig2(), { branch: opts.branch, brand });
361
445
  if (handled) return;
362
446
  }
@@ -1553,7 +1637,7 @@ function registerConfig(program) {
1553
1637
  }
1554
1638
 
1555
1639
  // src/commands/doctor.ts
1556
- import * as fs2 from "fs";
1640
+ import * as fs3 from "fs";
1557
1641
  function runDoctor(s, json) {
1558
1642
  const auth = !s.loggedIn ? "guest (not logged in)" : s.tokenExpired ? `logged in but token EXPIRED${s.tokenExpiresAt ? ` (${s.tokenExpiresAt})` : ""} \u2014 run bt login` : `logged in${s.tokenExpiresAt ? ` \xB7 token valid until ${s.tokenExpiresAt}` : ""}`;
1559
1643
  const lines = [
@@ -1575,7 +1659,7 @@ function gatherDoctorSnapshot(opts = {}) {
1575
1659
  const exp = jwtExpiry(token);
1576
1660
  let configExists = false;
1577
1661
  try {
1578
- configExists = fs2.existsSync(configPath());
1662
+ configExists = fs3.existsSync(configPath());
1579
1663
  } catch {
1580
1664
  }
1581
1665
  return {
@@ -1686,7 +1770,7 @@ async function runBlame(client, config, args) {
1686
1770
  return { lines, json: args.json ? { handle, lore } : void 0 };
1687
1771
  }
1688
1772
  function registerBlame(program, makeClient2, loadConfig2) {
1689
- program.command("blame", { hidden: true }).argument("[slug]", "a design \u2014 a slug, a bt browse number, or a fuzzy hint").description("who's responsible for a design \u2014 its lore + sources (git blame, for motifs).").action(async (slug) => {
1773
+ program.command("blame").argument("[slug]", "a design \u2014 a slug, a bt browse number, or a fuzzy hint").description("who's responsible for a design \u2014 its lore + sources (git blame, for motifs).").action(async (slug) => {
1690
1774
  const { json, brand } = resolveOutput(program);
1691
1775
  const res = await runBlame(makeClient2(), loadConfig2(), { handle: slug, json, brand });
1692
1776
  printResult(res, json);
@@ -1802,6 +1886,14 @@ if (isMainModule(process.argv[1], import.meta.url)) {
1802
1886
  if (out) process.stdout.write(out + "\n");
1803
1887
  if (err) process.stderr.write(err + "\n");
1804
1888
  process.exitCode = code;
1889
+ }).finally(() => {
1890
+ maybeNotifyUpdate({
1891
+ current: VERSION,
1892
+ env: process.env,
1893
+ argv: process.argv,
1894
+ stderrIsTTY: Boolean(process.stderr.isTTY),
1895
+ write: (s) => process.stderr.write(s)
1896
+ });
1805
1897
  });
1806
1898
  }
1807
1899
  export {