@component-compass/cli 0.1.26 → 0.1.27

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.
Files changed (2) hide show
  1. package/dist/cli.js +340 -228
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -7554,16 +7554,16 @@ var require_parse = __commonJS({
7554
7554
  const analysis = analyzeRepeatedExtglob(body, opts);
7555
7555
  if ((token.type === "plus" || token.type === "star") && analysis.risky) {
7556
7556
  const safeOutput = analysis.safeOutput ? (token.output ? "" : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) : void 0;
7557
- const open = tokens[token.tokensIndex];
7558
- open.type = "text";
7559
- open.value = literal;
7560
- open.output = safeOutput || utils.escapeRegex(literal);
7557
+ const open2 = tokens[token.tokensIndex];
7558
+ open2.type = "text";
7559
+ open2.value = literal;
7560
+ open2.output = safeOutput || utils.escapeRegex(literal);
7561
7561
  for (let i2 = token.tokensIndex + 1; i2 < tokens.length; i2++) {
7562
7562
  tokens[i2].value = "";
7563
7563
  tokens[i2].output = "";
7564
7564
  delete tokens[i2].suffix;
7565
7565
  }
7566
- state.output = token.output + open.output;
7566
+ state.output = token.output + open2.output;
7567
7567
  state.backtrack = true;
7568
7568
  push({ type: "paren", extglob: true, value, output: "" });
7569
7569
  decrement("parens");
@@ -7783,15 +7783,15 @@ var require_parse = __commonJS({
7783
7783
  }
7784
7784
  if (value === "{" && opts.nobrace !== true) {
7785
7785
  increment("braces");
7786
- const open = {
7786
+ const open2 = {
7787
7787
  type: "brace",
7788
7788
  value,
7789
7789
  output: "(",
7790
7790
  outputIndex: state.output.length,
7791
7791
  tokensIndex: state.tokens.length
7792
7792
  };
7793
- braces.push(open);
7794
- push(open);
7793
+ braces.push(open2);
7794
+ push(open2);
7795
7795
  continue;
7796
7796
  }
7797
7797
  if (value === "}") {
@@ -37172,9 +37172,9 @@ var require_is_glob = __commonJS({
37172
37172
  }
37173
37173
  }
37174
37174
  if (str[index] === "\\") {
37175
- var open = str[index + 1];
37175
+ var open2 = str[index + 1];
37176
37176
  index += 2;
37177
- var close = chars2[open];
37177
+ var close = chars2[open2];
37178
37178
  if (close) {
37179
37179
  var n2 = str.indexOf(close, index);
37180
37180
  if (n2 !== -1) {
@@ -37200,9 +37200,9 @@ var require_is_glob = __commonJS({
37200
37200
  return true;
37201
37201
  }
37202
37202
  if (str[index] === "\\") {
37203
- var open = str[index + 1];
37203
+ var open2 = str[index + 1];
37204
37204
  index += 2;
37205
- var close = chars2[open];
37205
+ var close = chars2[open2];
37206
37206
  if (close) {
37207
37207
  var n2 = str.indexOf(close, index);
37208
37208
  if (n2 !== -1) {
@@ -38166,7 +38166,7 @@ var require_parse2 = __commonJS({
38166
38166
  continue;
38167
38167
  }
38168
38168
  if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) {
38169
- const open = value;
38169
+ const open2 = value;
38170
38170
  let next;
38171
38171
  if (options.keepQuotes !== true) {
38172
38172
  value = "";
@@ -38176,7 +38176,7 @@ var require_parse2 = __commonJS({
38176
38176
  value += next + advance();
38177
38177
  continue;
38178
38178
  }
38179
- if (next === open) {
38179
+ if (next === open2) {
38180
38180
  if (options.keepQuotes === true) value += next;
38181
38181
  break;
38182
38182
  }
@@ -38219,8 +38219,8 @@ var require_parse2 = __commonJS({
38219
38219
  if (value === CHAR_COMMA && depth > 0) {
38220
38220
  if (block2.ranges > 0) {
38221
38221
  block2.ranges = 0;
38222
- const open = block2.nodes.shift();
38223
- block2.nodes = [open, { type: "text", value: stringify2(block2) }];
38222
+ const open2 = block2.nodes.shift();
38223
+ block2.nodes = [open2, { type: "text", value: stringify2(block2) }];
38224
38224
  }
38225
38225
  push({ type: "comma", value });
38226
38226
  block2.commas++;
@@ -39318,16 +39318,16 @@ var require_parse3 = __commonJS({
39318
39318
  const analysis = analyzeRepeatedExtglob(body, opts);
39319
39319
  if ((token.type === "plus" || token.type === "star") && analysis.risky) {
39320
39320
  const safeOutput = analysis.safeOutput ? (token.output ? "" : ONE_CHAR) + (opts.capture ? `(${analysis.safeOutput})` : analysis.safeOutput) : void 0;
39321
- const open = tokens[token.tokensIndex];
39322
- open.type = "text";
39323
- open.value = literal;
39324
- open.output = safeOutput || utils.escapeRegex(literal);
39321
+ const open2 = tokens[token.tokensIndex];
39322
+ open2.type = "text";
39323
+ open2.value = literal;
39324
+ open2.output = safeOutput || utils.escapeRegex(literal);
39325
39325
  for (let i2 = token.tokensIndex + 1; i2 < tokens.length; i2++) {
39326
39326
  tokens[i2].value = "";
39327
39327
  tokens[i2].output = "";
39328
39328
  delete tokens[i2].suffix;
39329
39329
  }
39330
- state.output = token.output + open.output;
39330
+ state.output = token.output + open2.output;
39331
39331
  state.backtrack = true;
39332
39332
  push({ type: "paren", extglob: true, value, output: "" });
39333
39333
  decrement("parens");
@@ -39547,15 +39547,15 @@ var require_parse3 = __commonJS({
39547
39547
  }
39548
39548
  if (value === "{" && opts.nobrace !== true) {
39549
39549
  increment("braces");
39550
- const open = {
39550
+ const open2 = {
39551
39551
  type: "brace",
39552
39552
  value,
39553
39553
  output: "(",
39554
39554
  outputIndex: state.output.length,
39555
39555
  tokensIndex: state.tokens.length
39556
39556
  };
39557
- braces.push(open);
39558
- push(open);
39557
+ braces.push(open2);
39558
+ push(open2);
39559
39559
  continue;
39560
39560
  }
39561
39561
  if (value === "}") {
@@ -40350,8 +40350,8 @@ var require_pattern2 = __commonJS({
40350
40350
  }
40351
40351
  exports.endsWithSlashGlobStar = endsWithSlashGlobStar;
40352
40352
  function isAffectDepthOfReadingPattern(pattern) {
40353
- const basename6 = path5.basename(pattern);
40354
- return endsWithSlashGlobStar(pattern) || isStaticPattern(basename6);
40353
+ const basename7 = path5.basename(pattern);
40354
+ return endsWithSlashGlobStar(pattern) || isStaticPattern(basename7);
40355
40355
  }
40356
40356
  exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern;
40357
40357
  function expandPatternsWithBraceExpansion(patterns) {
@@ -43241,7 +43241,7 @@ async function pollUpload(opts) {
43241
43241
  status = await inspect3().catch(async (err2) => {
43242
43242
  if (!(err2 instanceof UploadError) || err2.code !== 401) throw err2;
43243
43243
  signal.throwIfAborted();
43244
- token = await opts.refreshToken();
43244
+ token = await opts.onUnauthorized();
43245
43245
  return await inspect3();
43246
43246
  });
43247
43247
  } catch (err2) {
@@ -43267,8 +43267,8 @@ async function pollUpload(opts) {
43267
43267
  }
43268
43268
  hint = status.retryAfterSeconds;
43269
43269
  }
43270
- const delay = hint !== void 0 ? hint * 1e3 : backoffDelay(attempt);
43271
- await wait(Math.min(2147483647, Math.max(1e3, delay)), signal);
43270
+ const delay2 = hint !== void 0 ? hint * 1e3 : backoffDelay(attempt);
43271
+ await wait(Math.min(2147483647, Math.max(1e3, delay2)), signal);
43272
43272
  }
43273
43273
  }
43274
43274
  try {
@@ -43282,7 +43282,7 @@ async function pollUpload(opts) {
43282
43282
  controller.abort();
43283
43283
  }
43284
43284
  }
43285
- function wait(delay, signal) {
43285
+ function wait(delay2, signal) {
43286
43286
  return new Promise((resolve14, reject) => {
43287
43287
  signal?.throwIfAborted();
43288
43288
  const abort = () => {
@@ -43292,7 +43292,7 @@ function wait(delay, signal) {
43292
43292
  const timer = setTimeout(() => {
43293
43293
  signal?.removeEventListener("abort", abort);
43294
43294
  resolve14();
43295
- }, delay);
43295
+ }, delay2);
43296
43296
  signal?.addEventListener("abort", abort, { once: true });
43297
43297
  });
43298
43298
  }
@@ -43389,8 +43389,10 @@ var init_paths = __esm({
43389
43389
  });
43390
43390
 
43391
43391
  // src/auth/store.ts
43392
- import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2, chmod } from "node:fs/promises";
43393
- import { dirname as dirname12 } from "node:path";
43392
+ import { mkdir as mkdir2, readFile as readFile4, writeFile as writeFile2, chmod, rename, unlink, open } from "node:fs/promises";
43393
+ import { randomUUID } from "node:crypto";
43394
+ import { basename as basename5, dirname as dirname12, join as join16 } from "node:path";
43395
+ import { setTimeout as delay } from "node:timers/promises";
43394
43396
  function normalizeHost(input2) {
43395
43397
  const trimmed = input2.trim();
43396
43398
  if (trimmed === "") throw new Error("Host must not be empty.");
@@ -43411,107 +43413,85 @@ async function loadStore(filePath = hostsFilePath()) {
43411
43413
  } catch {
43412
43414
  throw new Error(`Token store at ${filePath} contains invalid JSON. Delete it and run \`component-compass auth login\` again.`);
43413
43415
  }
43414
- return { ...parsed.default ? { default: parsed.default } : {}, hosts: parsed.hosts ?? {} };
43416
+ const value = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? parsed : {};
43417
+ const entries = typeof value.hosts === "object" && value.hosts !== null && !Array.isArray(value.hosts) ? Object.entries(value.hosts) : [];
43418
+ const hosts = Object.fromEntries(entries.flatMap(([host, entry]) => {
43419
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return [];
43420
+ const candidate = entry;
43421
+ return typeof candidate.token === "string" && typeof candidate.userEmail === "string" ? [[host, { token: candidate.token, userEmail: candidate.userEmail }]] : [];
43422
+ }));
43423
+ return { ...typeof value.default === "string" && value.default ? { default: value.default } : {}, hosts };
43415
43424
  }
43416
- async function saveStore(store, filePath = hostsFilePath()) {
43425
+ async function writeStore(store, filePath) {
43417
43426
  await mkdir2(dirname12(filePath), { recursive: true, mode: 448 });
43418
- await writeFile2(filePath, `${JSON.stringify(store, null, 2)}
43419
- `, { mode: 384 });
43420
- await chmod(filePath, 384);
43421
- }
43422
- var init_store = __esm({
43423
- "src/auth/store.ts"() {
43424
- "use strict";
43425
- init_paths();
43427
+ const tempPath = join16(dirname12(filePath), `.${basename5(filePath)}.${randomUUID()}.tmp`);
43428
+ try {
43429
+ await writeFile2(tempPath, `${JSON.stringify(store, null, 2)}
43430
+ `, { mode: 384, flag: "wx" });
43431
+ await chmod(tempPath, 384);
43432
+ await rename(tempPath, filePath);
43433
+ } finally {
43434
+ await unlink(tempPath).catch((error2) => {
43435
+ if (error2.code !== "ENOENT") throw error2;
43436
+ });
43426
43437
  }
43427
- });
43428
-
43429
- // src/auth/client.ts
43430
- function endpoint(base2, path5) {
43431
- return `${base2.replace(/\/+$/, "")}${path5}`;
43432
- }
43433
- async function readError(res) {
43434
- const body = await res.json().catch(() => ({}));
43435
- return body.error ?? "unknown_error";
43436
- }
43437
- function toTokenPair(body) {
43438
- return {
43439
- accessToken: body.access_token,
43440
- refreshToken: body.refresh_token,
43441
- expiresIn: body.expires_in,
43442
- ...body.email !== void 0 ? { email: body.email } : {}
43443
- };
43444
- }
43445
- async function requestDeviceCode(base2) {
43446
- const res = await fetch(endpoint(base2, "/api/auth/cli/device-code"), { method: "POST" });
43447
- if (!res.ok) throw new AuthHttpError(res.status, `device-code failed: ${await readError(res)}`);
43448
- const b = await res.json();
43449
- return {
43450
- deviceCode: b.device_code,
43451
- userCode: b.user_code,
43452
- verificationUri: b.verification_uri,
43453
- verificationUriComplete: b.verification_uri_complete,
43454
- expiresIn: b.expires_in,
43455
- interval: b.interval
43456
- };
43457
43438
  }
43458
- async function pollToken(base2, deviceCode) {
43459
- const res = await fetch(endpoint(base2, "/api/auth/cli/token"), {
43460
- method: "POST",
43461
- headers: { "Content-Type": "application/json" },
43462
- body: JSON.stringify({ device_code: deviceCode })
43463
- });
43464
- if (res.ok) {
43465
- return { kind: "tokens", tokens: toTokenPair(await res.json()) };
43466
- }
43467
- const error2 = await readError(res);
43468
- switch (error2) {
43469
- case "authorization_pending":
43470
- return { kind: "pending" };
43471
- case "slow_down":
43472
- return { kind: "slow_down" };
43473
- case "expired_token":
43474
- return { kind: "expired" };
43475
- case "access_denied":
43476
- return { kind: "denied" };
43477
- default:
43478
- throw new AuthHttpError(res.status, `token poll failed: ${error2}`);
43439
+ async function releaseStoreLock(lockPath, owner) {
43440
+ try {
43441
+ if (await readFile4(lockPath, "utf8") === owner) await unlink(lockPath);
43442
+ } catch (error2) {
43443
+ if (error2.code !== "ENOENT") throw error2;
43479
43444
  }
43480
43445
  }
43481
- async function refreshTokens(base2, refreshToken) {
43482
- let res;
43446
+ async function withStoreLock(filePath, action) {
43447
+ await mkdir2(dirname12(filePath), { recursive: true, mode: 448 });
43448
+ const lockPath = `${filePath}.lock`;
43449
+ const owner = `${process.pid}:${randomUUID()}`;
43450
+ const deadline = Date.now() + LOCK_WAIT_MS;
43451
+ while (true) {
43452
+ if (Date.now() >= deadline) {
43453
+ throw new StoreLockError(`Timed out waiting for credential store lock at ${lockPath}. Retry, or remove it only after confirming no CLI process is running.`);
43454
+ }
43455
+ try {
43456
+ const handle = await open(lockPath, "wx", 384);
43457
+ try {
43458
+ await handle.writeFile(owner);
43459
+ await handle.close();
43460
+ } catch (error2) {
43461
+ await handle.close().catch(() => {
43462
+ });
43463
+ await unlink(lockPath).catch(() => {
43464
+ });
43465
+ throw error2;
43466
+ }
43467
+ break;
43468
+ } catch (error2) {
43469
+ if (error2.code !== "EEXIST") throw error2;
43470
+ await delay(LOCK_POLL_MS);
43471
+ }
43472
+ }
43483
43473
  try {
43484
- res = await fetch(endpoint(base2, "/api/auth/cli/refresh"), {
43485
- method: "POST",
43486
- headers: { "Content-Type": "application/json" },
43487
- body: JSON.stringify({ refresh_token: refreshToken })
43488
- });
43489
- } catch (err2) {
43490
- return { kind: "transient", error: err2 instanceof Error ? err2.message : "network_error" };
43474
+ return await action();
43475
+ } finally {
43476
+ await releaseStoreLock(lockPath, owner);
43491
43477
  }
43492
- if (res.ok) return { kind: "tokens", tokens: toTokenPair(await res.json()) };
43493
- const error2 = await readError(res);
43494
- const terminal = res.status === 401 && ["invalid_token", "sso_revoked", "not_authorized"].includes(error2);
43495
- return terminal ? { kind: "terminal", error: error2 } : { kind: "transient", error: error2 };
43496
43478
  }
43497
- async function whoami(base2, accessToken) {
43498
- const res = await fetch(endpoint(base2, "/api/auth/cli/whoami"), {
43499
- headers: { Authorization: `Bearer ${accessToken}` }
43479
+ async function mutateStore(change, filePath = hostsFilePath()) {
43480
+ return await withStoreLock(filePath, async () => {
43481
+ const store = await loadStore(filePath);
43482
+ const { changed, result: result2 } = change(store);
43483
+ if (changed) await writeStore(store, filePath);
43484
+ return result2;
43500
43485
  });
43501
- if (res.status === 401) return null;
43502
- if (!res.ok) throw new AuthHttpError(res.status, `whoami failed: ${await readError(res)}`);
43503
- return await res.json();
43504
43486
  }
43505
- var AuthHttpError;
43506
- var init_client = __esm({
43507
- "src/auth/client.ts"() {
43487
+ var LOCK_WAIT_MS, LOCK_POLL_MS, StoreLockError;
43488
+ var init_store = __esm({
43489
+ "src/auth/store.ts"() {
43508
43490
  "use strict";
43509
- AuthHttpError = class extends Error {
43510
- constructor(status, message) {
43511
- super(message);
43512
- this.status = status;
43513
- }
43514
- status;
43491
+ init_paths();
43492
+ LOCK_WAIT_MS = 5e3;
43493
+ LOCK_POLL_MS = 25;
43494
+ StoreLockError = class extends Error {
43515
43495
  };
43516
43496
  }
43517
43497
  });
@@ -43519,12 +43499,15 @@ var init_client = __esm({
43519
43499
  // src/auth/session.ts
43520
43500
  var session_exports = {};
43521
43501
  __export(session_exports, {
43522
- IdpUnavailableError: () => IdpUnavailableError,
43502
+ HostUnavailableError: () => HostUnavailableError,
43523
43503
  NotSignedInError: () => NotSignedInError,
43524
43504
  ReloginRequiredError: () => ReloginRequiredError,
43505
+ SessionChangedError: () => SessionChangedError,
43525
43506
  formatAuthError: () => formatAuthError,
43526
- getAccessToken: () => getAccessToken,
43527
- resolveHost: () => resolveHost
43507
+ getStoredSession: () => getStoredSession,
43508
+ removeStoredSession: () => removeStoredSession,
43509
+ resolveHost: () => resolveHost,
43510
+ setStoredSession: () => setStoredSession
43528
43511
  });
43529
43512
  function resolveHost(opts) {
43530
43513
  const env2 = opts.env ?? process.env;
@@ -43536,52 +43519,39 @@ function resolveHost(opts) {
43536
43519
  }
43537
43520
  return normalizeHost(raw);
43538
43521
  }
43539
- async function getAccessToken(host, deps = {}) {
43540
- const now = deps.now ?? Date.now;
43522
+ async function getStoredSession(host, deps = {}) {
43541
43523
  const base2 = normalizeHost(host);
43542
43524
  const store = await loadStore(deps.filePath);
43543
43525
  const entry = store.hosts[base2];
43544
43526
  if (!entry) throw new NotSignedInError(base2);
43545
- const stillValid = Date.parse(entry.expiresAt) - now() > EXPIRY_SKEW_MS;
43546
- if (stillValid && !deps.force) {
43547
- return { accessToken: entry.accessToken, userEmail: entry.userEmail };
43548
- }
43549
- const result2 = await refreshTokens(base2, entry.refreshToken);
43550
- if (result2.kind === "tokens") {
43551
- const updated = {
43552
- accessToken: result2.tokens.accessToken,
43553
- refreshToken: result2.tokens.refreshToken,
43554
- userEmail: result2.tokens.email ?? entry.userEmail,
43555
- expiresAt: new Date(now() + result2.tokens.expiresIn * 1e3).toISOString()
43556
- };
43557
- store.hosts[base2] = updated;
43558
- await saveStore(store, deps.filePath);
43559
- return { accessToken: updated.accessToken, userEmail: updated.userEmail };
43560
- }
43561
- if (result2.kind === "terminal") {
43562
- const { [base2]: _removed, ...remainingHosts } = store.hosts;
43563
- const cleared = {
43564
- hosts: remainingHosts,
43565
- ...store.default && store.default !== base2 ? { default: store.default } : {}
43566
- };
43567
- await saveStore(cleared, deps.filePath);
43568
- throw new ReloginRequiredError(base2);
43569
- }
43570
- throw new IdpUnavailableError(base2, result2.error);
43527
+ return entry;
43528
+ }
43529
+ async function setStoredSession(host, entry, deps = {}) {
43530
+ const base2 = normalizeHost(host);
43531
+ await mutateStore((store) => {
43532
+ store.hosts[base2] = entry;
43533
+ if (!store.default) store.default = base2;
43534
+ return { changed: true, result: void 0 };
43535
+ }, deps.filePath);
43536
+ }
43537
+ async function removeStoredSession(host, deps) {
43538
+ const base2 = normalizeHost(host);
43539
+ return await mutateStore((store) => {
43540
+ if (store.hosts[base2]?.token !== deps.expectedToken) return { changed: false, result: false };
43541
+ const { [base2]: _removed, ...hosts } = store.hosts;
43542
+ store.hosts = hosts;
43543
+ if (store.default === base2) store.default = void 0;
43544
+ return { changed: true, result: true };
43545
+ }, deps.filePath);
43571
43546
  }
43572
43547
  function formatAuthError(err2) {
43573
- if (err2 instanceof NotSignedInError || err2 instanceof ReloginRequiredError || err2 instanceof IdpUnavailableError) {
43574
- return err2.message;
43575
- }
43576
- return null;
43548
+ return err2 instanceof NotSignedInError || err2 instanceof ReloginRequiredError || err2 instanceof SessionChangedError || err2 instanceof HostUnavailableError ? err2.message : null;
43577
43549
  }
43578
- var EXPIRY_SKEW_MS, NotSignedInError, ReloginRequiredError, IdpUnavailableError;
43550
+ var NotSignedInError, ReloginRequiredError, SessionChangedError, HostUnavailableError;
43579
43551
  var init_session = __esm({
43580
43552
  "src/auth/session.ts"() {
43581
43553
  "use strict";
43582
43554
  init_store();
43583
- init_client();
43584
- EXPIRY_SKEW_MS = 6e4;
43585
43555
  NotSignedInError = class extends Error {
43586
43556
  constructor(host) {
43587
43557
  super(`Not signed in to ${host}. Run \`component-compass auth login --host ${host}\`.`);
@@ -43591,19 +43561,24 @@ var init_session = __esm({
43591
43561
  };
43592
43562
  ReloginRequiredError = class extends Error {
43593
43563
  constructor(host) {
43594
- super(`Session for ${host} expired or was revoked. Run \`component-compass auth login --host ${host}\`.`);
43564
+ super(`Session for ${host} is no longer valid. Run \`component-compass auth login --host ${host}\`.`);
43565
+ this.host = host;
43566
+ }
43567
+ host;
43568
+ };
43569
+ SessionChangedError = class extends Error {
43570
+ constructor(host) {
43571
+ super(`Session changed for ${host}; retry the upload with the current sign-in.`);
43595
43572
  this.host = host;
43596
43573
  }
43597
43574
  host;
43598
43575
  };
43599
- IdpUnavailableError = class extends Error {
43600
- constructor(host, reason) {
43601
- super(`Could not reach the identity provider to refresh credentials for ${host} (${reason}). Check your network and retry.`);
43576
+ HostUnavailableError = class extends Error {
43577
+ constructor(host) {
43578
+ super(`Could not reach ${host} to check the session. Check your network and retry.`);
43602
43579
  this.host = host;
43603
- this.reason = reason;
43604
43580
  }
43605
43581
  host;
43606
- reason;
43607
43582
  };
43608
43583
  }
43609
43584
  });
@@ -43623,28 +43598,27 @@ async function createAuthedUploader(opts) {
43623
43598
  });
43624
43599
  const { COMPONENT_COMPASS_TOKEN: ciToken } = process.env;
43625
43600
  const session = filePath !== void 0 ? { filePath } : {};
43626
- let token = ciToken || (await getAccessToken(base2, session)).accessToken;
43627
- async function refreshToken() {
43601
+ const token = ciToken || (await getStoredSession(base2, session)).token;
43602
+ async function onUnauthorized() {
43628
43603
  if (ciToken) {
43629
- throw new UploadError(401, "COMPONENT_COMPASS_TOKEN was rejected (401); interactive login and token refresh are disabled. Supply a valid token.");
43604
+ throw new UploadError(401, "COMPONENT_COMPASS_TOKEN was rejected (401). Supply a valid token.");
43630
43605
  }
43631
- token = (await getAccessToken(base2, { ...session, force: true })).accessToken;
43632
- return token;
43606
+ const removed = await removeStoredSession(base2, { expectedToken: token, ...session });
43607
+ if (!removed) throw new SessionChangedError(base2);
43608
+ throw new ReloginRequiredError(base2);
43633
43609
  }
43634
43610
  return {
43635
43611
  base: base2,
43636
43612
  async upload(artifactJson) {
43637
- const submit = () => submitArtifact({ host: base2, token, artifactJson });
43638
- const receipt = await submit().catch(async (err2) => {
43613
+ const receipt = await submitArtifact({ host: base2, token, artifactJson }).catch(async (err2) => {
43639
43614
  if (!(err2 instanceof UploadError) || err2.code !== 401) throw err2;
43640
- await refreshToken();
43641
- return await submit();
43615
+ return await onUnauthorized();
43642
43616
  });
43643
43617
  return await pollUpload({
43644
43618
  host: base2,
43645
43619
  receipt,
43646
43620
  getToken: async () => token,
43647
- refreshToken,
43621
+ onUnauthorized,
43648
43622
  timeoutMs: 3e5,
43649
43623
  ...opts.onStatus ? { onStatus: opts.onStatus } : {}
43650
43624
  });
@@ -45151,6 +45125,105 @@ var init_parse = __esm({
45151
45125
  }
45152
45126
  });
45153
45127
 
45128
+ // src/auth/client.ts
45129
+ function endpoint(base2, path5) {
45130
+ return `${base2.replace(/\/+$/, "")}${path5}`;
45131
+ }
45132
+ async function readError(res) {
45133
+ const body = await res.json().catch(() => ({}));
45134
+ return body.error ?? "unknown_error";
45135
+ }
45136
+ function toUserSession(body) {
45137
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
45138
+ throw new AuthProtocolError("Invalid token response: missing session.");
45139
+ }
45140
+ const { access_token: token, token_type: tokenType, email } = body;
45141
+ if (typeof token !== "string" || !USER_SESSION_TOKEN_PATTERN.test(token) || tokenType !== "Bearer" || typeof email !== "string" || email.trim() === "") {
45142
+ throw new AuthProtocolError("Invalid token response: malformed session.");
45143
+ }
45144
+ return { token, email };
45145
+ }
45146
+ async function requestDeviceCode(base2) {
45147
+ const res = await fetch(endpoint(base2, "/api/auth/cli/device-code"), { method: "POST" });
45148
+ if (!res.ok) throw new AuthHttpError(res.status, `device-code failed: ${await readError(res)}`);
45149
+ const b = await res.json();
45150
+ return {
45151
+ deviceCode: b.device_code,
45152
+ userCode: b.user_code,
45153
+ verificationUri: b.verification_uri,
45154
+ verificationUriComplete: b.verification_uri_complete,
45155
+ expiresIn: b.expires_in,
45156
+ interval: b.interval
45157
+ };
45158
+ }
45159
+ async function pollToken(base2, deviceCode) {
45160
+ const res = await fetch(endpoint(base2, "/api/auth/cli/token"), {
45161
+ method: "POST",
45162
+ headers: { "Content-Type": "application/json" },
45163
+ body: JSON.stringify({ device_code: deviceCode })
45164
+ });
45165
+ if (res.ok) {
45166
+ const body = await res.json().catch(() => {
45167
+ throw new AuthProtocolError("Invalid token response: could not parse JSON.");
45168
+ });
45169
+ return { kind: "session", session: toUserSession(body) };
45170
+ }
45171
+ const error2 = await readError(res);
45172
+ switch (error2) {
45173
+ case "authorization_pending":
45174
+ return { kind: "pending" };
45175
+ case "slow_down":
45176
+ return { kind: "slow_down" };
45177
+ case "expired_token":
45178
+ return { kind: "expired" };
45179
+ case "access_denied":
45180
+ return { kind: "denied" };
45181
+ default:
45182
+ throw new AuthHttpError(res.status, `token poll failed: ${error2}`);
45183
+ }
45184
+ }
45185
+ async function revokeSession(base2, token) {
45186
+ const res = await fetch(endpoint(base2, "/api/auth/cli/session"), {
45187
+ method: "DELETE",
45188
+ headers: { Authorization: `Bearer ${token}` }
45189
+ });
45190
+ if (res.status !== 204) throw new AuthHttpError(res.status, `session revoke failed: ${await readError(res)}`);
45191
+ }
45192
+ async function whoami(base2, token) {
45193
+ const res = await fetch(endpoint(base2, "/api/auth/cli/whoami"), {
45194
+ headers: { Authorization: `Bearer ${token}` }
45195
+ });
45196
+ if (res.status === 401) return null;
45197
+ if (!res.ok) throw new AuthHttpError(res.status, `whoami failed: ${await readError(res)}`);
45198
+ const body = await res.json().catch(() => {
45199
+ throw new AuthProtocolError("Invalid whoami response: could not parse JSON.");
45200
+ });
45201
+ if (typeof body !== "object" || body === null || Array.isArray(body)) {
45202
+ throw new AuthProtocolError("Invalid whoami response: missing identity.");
45203
+ }
45204
+ const { userId, email } = body;
45205
+ if (typeof userId !== "string" || userId === "" || typeof email !== "string" && email !== null) {
45206
+ throw new AuthProtocolError("Invalid whoami response: malformed identity.");
45207
+ }
45208
+ return { userId, email };
45209
+ }
45210
+ var AuthHttpError, AuthProtocolError, USER_SESSION_TOKEN_PATTERN;
45211
+ var init_client = __esm({
45212
+ "src/auth/client.ts"() {
45213
+ "use strict";
45214
+ AuthHttpError = class extends Error {
45215
+ constructor(status, message) {
45216
+ super(message);
45217
+ this.status = status;
45218
+ }
45219
+ status;
45220
+ };
45221
+ AuthProtocolError = class extends Error {
45222
+ };
45223
+ USER_SESSION_TOKEN_PATTERN = /^cc_u_[A-Za-z0-9_-]{43}$/;
45224
+ }
45225
+ });
45226
+
45154
45227
  // src/auth/browser.ts
45155
45228
  import { spawn as spawn2 } from "node:child_process";
45156
45229
  function openBrowser(url, deps = {}) {
@@ -45205,6 +45278,33 @@ async function runAuthLogin(opts) {
45205
45278
  const sleep = opts.sleep ?? defaultSleep;
45206
45279
  const now = opts.now ?? Date.now;
45207
45280
  const filePath = opts.store?.filePath;
45281
+ const stored = (await loadStore(filePath)).hosts[base2];
45282
+ if (stored) {
45283
+ try {
45284
+ const identity = await whoami(base2, stored.token);
45285
+ if (identity) {
45286
+ out(`Already signed in as ${identity.email ?? stored.userEmail} to ${base2}.
45287
+ `);
45288
+ return 0;
45289
+ }
45290
+ const removed = await removeStoredSession(base2, { expectedToken: stored.token, ...filePath !== void 0 ? { filePath } : {} });
45291
+ if (!removed) {
45292
+ err(`Session for ${base2} changed while checking it. Run \`component-compass auth login --host ${base2}\` again.
45293
+ `);
45294
+ return 1;
45295
+ }
45296
+ } catch (e) {
45297
+ if (e instanceof StoreLockError) err(`${e.message}
45298
+ `);
45299
+ else if (e instanceof AuthHttpError) err(`Login failed: ${base2} returned HTTP ${e.status}. Your stored session was kept.
45300
+ `);
45301
+ else if (e instanceof AuthProtocolError) err(`Login failed: ${e.message} Your stored session was kept.
45302
+ `);
45303
+ else err(`${new HostUnavailableError(base2).message}
45304
+ `);
45305
+ return 1;
45306
+ }
45307
+ }
45208
45308
  let device;
45209
45309
  try {
45210
45310
  device = await requestDeviceCode(base2);
@@ -45239,6 +45339,10 @@ Code: ${device.userCode}
45239
45339
  if (e instanceof AuthHttpError) {
45240
45340
  err(`
45241
45341
  Login failed: ${base2} returned HTTP ${e.status}. Run \`component-compass auth login\` again.
45342
+ `);
45343
+ } else if (e instanceof AuthProtocolError) {
45344
+ err(`
45345
+ Login failed: ${e.message} Your stored sessions were kept.
45242
45346
  `);
45243
45347
  } else {
45244
45348
  const detail = transportDetail(e);
@@ -45248,17 +45352,12 @@ Could not reach ${base2}${detail ? ` (${detail})` : ""}. Check the host URL and
45248
45352
  }
45249
45353
  return 1;
45250
45354
  }
45251
- if (result2.kind === "tokens") {
45355
+ if (result2.kind === "session") {
45252
45356
  const entry = {
45253
- accessToken: result2.tokens.accessToken,
45254
- refreshToken: result2.tokens.refreshToken,
45255
- userEmail: result2.tokens.email ?? "",
45256
- expiresAt: new Date(now() + result2.tokens.expiresIn * 1e3).toISOString()
45257
- };
45258
- const store = await loadStore(filePath);
45259
- store.hosts[base2] = entry;
45260
- if (!store.default) store.default = base2;
45261
- await saveStore(store, filePath);
45357
+ token: result2.session.token,
45358
+ userEmail: result2.session.email
45359
+ };
45360
+ await setStoredSession(base2, entry, filePath !== void 0 ? { filePath } : {});
45262
45361
  if (spin) spin.stop("Approved");
45263
45362
  out(`\u2713 Signed in as ${entry.userEmail || "your account"}
45264
45363
  `);
@@ -45294,19 +45393,27 @@ async function runAuthStatus(opts) {
45294
45393
  write3("Not signed in.\n");
45295
45394
  return 1;
45296
45395
  }
45297
- if (!store.hosts[base2]) {
45396
+ const entry = store.hosts[base2];
45397
+ if (!entry) {
45298
45398
  write3(`Not signed in to ${base2}.
45299
45399
  `);
45300
45400
  return 1;
45301
45401
  }
45302
45402
  try {
45303
- const { accessToken, userEmail } = await getAccessToken(base2, filePath !== void 0 ? { filePath } : {});
45304
- const who = await whoami(base2, accessToken);
45305
- write3(`Signed in as ${who?.email ?? userEmail} to ${base2}.
45403
+ const { token, userEmail } = await getStoredSession(base2, filePath !== void 0 ? { filePath } : {});
45404
+ const who = await whoami(base2, token);
45405
+ if (!who) {
45406
+ const removed = await removeStoredSession(base2, { expectedToken: token, ...filePath !== void 0 ? { filePath } : {} });
45407
+ write3(removed ? `${new ReloginRequiredError(base2).message}
45408
+ ` : `Session changed for ${base2}; retry \`component-compass auth status --host ${base2}\`.
45409
+ `);
45410
+ return 1;
45411
+ }
45412
+ write3(`Signed in as ${who.email ?? userEmail} to ${base2}.
45306
45413
  `);
45307
45414
  return 0;
45308
45415
  } catch (e) {
45309
- const msg = formatAuthError(e) ?? (e instanceof Error ? e.message : String(e));
45416
+ const msg = e instanceof StoreLockError ? e.message : formatAuthError(e) ?? (e instanceof AuthHttpError ? `Host ${base2} returned HTTP ${e.status}. Your stored session was kept.` : e instanceof AuthProtocolError ? `${e.message} Your stored session was kept.` : new HostUnavailableError(base2).message);
45310
45417
  write3(`${msg}
45311
45418
  `);
45312
45419
  return 1;
@@ -45323,18 +45430,23 @@ async function runAuthLogout(opts) {
45323
45430
  write3("Not signed in to anything.\n");
45324
45431
  return 0;
45325
45432
  }
45326
- if (!store.hosts[base2]) {
45433
+ const entry = store.hosts[base2];
45434
+ if (!entry) {
45327
45435
  write3(`Not signed in to ${base2}.
45328
45436
  `);
45329
45437
  return 0;
45330
45438
  }
45331
- const { [base2]: _removed, ...remaining } = store.hosts;
45332
- const next = {
45333
- hosts: remaining,
45334
- ...store.default && store.default !== base2 ? { default: store.default } : {}
45335
- };
45336
- await saveStore(next, filePath);
45337
- write3(`Signed out of ${base2}.
45439
+ try {
45440
+ await revokeSession(base2, entry.token);
45441
+ } catch (e) {
45442
+ write3(e instanceof AuthHttpError ? `Could not revoke the session for ${base2}: HTTP ${e.status}. Your stored session was kept.
45443
+ ` : `${new HostUnavailableError(base2).message} Your stored session was kept.
45444
+ `);
45445
+ return 1;
45446
+ }
45447
+ const removed = await removeStoredSession(base2, { expectedToken: entry.token, ...filePath !== void 0 ? { filePath } : {} });
45448
+ write3(removed ? `Signed out of ${base2}.
45449
+ ` : `Revoked the previous session for ${base2}; newer sign-in kept.
45338
45450
  `);
45339
45451
  return 0;
45340
45452
  }
@@ -67524,7 +67636,7 @@ function requireUtil$2() {
67524
67636
  return path5;
67525
67637
  });
67526
67638
  exports$1.normalize = normalize2;
67527
- function join16(aRoot, aPath) {
67639
+ function join17(aRoot, aPath) {
67528
67640
  if (aRoot === "") {
67529
67641
  aRoot = ".";
67530
67642
  }
@@ -67556,7 +67668,7 @@ function requireUtil$2() {
67556
67668
  }
67557
67669
  return joined;
67558
67670
  }
67559
- exports$1.join = join16;
67671
+ exports$1.join = join17;
67560
67672
  exports$1.isAbsolute = function(aPath) {
67561
67673
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
67562
67674
  };
@@ -67770,7 +67882,7 @@ function requireUtil$2() {
67770
67882
  parsed.path = parsed.path.substring(0, index + 1);
67771
67883
  }
67772
67884
  }
67773
- sourceURL = join16(urlGenerate(parsed), sourceURL);
67885
+ sourceURL = join17(urlGenerate(parsed), sourceURL);
67774
67886
  }
67775
67887
  return normalize2(sourceURL);
67776
67888
  }
@@ -75525,9 +75637,9 @@ function requirePicocolors() {
75525
75637
  hasRequiredPicocolors = 1;
75526
75638
  let p = browser$1 || {}, argv2 = p.argv || [], env2 = p.env || {};
75527
75639
  let isColorSupported = !(!!env2.NO_COLOR || argv2.includes("--no-color")) && (!!env2.FORCE_COLOR || argv2.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env2.TERM !== "dumb" || !!env2.CI);
75528
- let formatter = (open, close, replace = open) => (input2) => {
75529
- let string2 = "" + input2, index = string2.indexOf(close, open.length);
75530
- return ~index ? open + replaceClose(string2, close, replace, index) + close : open + string2 + close;
75640
+ let formatter = (open2, close, replace = open2) => (input2) => {
75641
+ let string2 = "" + input2, index = string2.indexOf(close, open2.length);
75642
+ return ~index ? open2 + replaceClose(string2, close, replace, index) + close : open2 + string2 + close;
75531
75643
  };
75532
75644
  let replaceClose = (string2, close, replace, index) => {
75533
75645
  let result2 = "", cursor3 = 0;
@@ -77190,7 +77302,7 @@ function requirePreviousMap() {
77190
77302
  if (hasRequiredPreviousMap) return previousMap;
77191
77303
  hasRequiredPreviousMap = 1;
77192
77304
  let { existsSync: existsSync8, readFileSync: readFileSync13 } = require$$0$1;
77193
- let { dirname: dirname13, join: join16 } = require$$1;
77305
+ let { dirname: dirname13, join: join17 } = require$$1;
77194
77306
  let { SourceMapConsumer, SourceMapGenerator } = /* @__PURE__ */ requireSourceMap$1();
77195
77307
  function fromBase64(str) {
77196
77308
  if (Buffer$1) {
@@ -77289,7 +77401,7 @@ function requirePreviousMap() {
77289
77401
  return this.decodeInline(this.annotation);
77290
77402
  } else if (this.annotation) {
77291
77403
  let map2 = this.annotation;
77292
- if (file) map2 = join16(dirname13(file), map2);
77404
+ if (file) map2 = join17(dirname13(file), map2);
77293
77405
  return this.loadFile(map2);
77294
77406
  }
77295
77407
  }
@@ -78095,7 +78207,7 @@ function requireParser$1() {
78095
78207
  let prev;
78096
78208
  let shift;
78097
78209
  let last = false;
78098
- let open = false;
78210
+ let open2 = false;
78099
78211
  let params = [];
78100
78212
  let brackets = [];
78101
78213
  while (!this.tokenizer.endOfFile()) {
@@ -78115,7 +78227,7 @@ function requireParser$1() {
78115
78227
  this.semicolon = true;
78116
78228
  break;
78117
78229
  } else if (type === "{") {
78118
- open = true;
78230
+ open2 = true;
78119
78231
  break;
78120
78232
  } else if (type === "}") {
78121
78233
  if (params.length > 0) {
@@ -78157,7 +78269,7 @@ function requireParser$1() {
78157
78269
  node2.raws.afterName = "";
78158
78270
  node2.params = "";
78159
78271
  }
78160
- if (open) {
78272
+ if (open2) {
78161
78273
  node2.nodes = [];
78162
78274
  this.current = node2;
78163
78275
  }
@@ -94097,11 +94209,11 @@ function createImportResolver(opts) {
94097
94209
  }
94098
94210
  if (spec.startsWith(".")) {
94099
94211
  const segments = spec.split("/");
94100
- const basename6 = segments[segments.length - 1];
94101
- if (basename6) {
94212
+ const basename7 = segments[segments.length - 1];
94213
+ if (basename7) {
94102
94214
  for (const ext of FILE_EXTENSIONS) {
94103
94215
  try {
94104
- return requireFromRepo.resolve(`${spec}/${basename6}${ext}`, {
94216
+ return requireFromRepo.resolve(`${spec}/${basename7}${ext}`, {
94105
94217
  paths: [fromDir, opts.repoRoot]
94106
94218
  });
94107
94219
  } catch {
@@ -94142,10 +94254,10 @@ function tryFileWithExtensions(absPath) {
94142
94254
  if (isFile(indexPath)) return indexPath;
94143
94255
  }
94144
94256
  const segments = absPath.split(/[\\/]/);
94145
- const basename6 = segments[segments.length - 1];
94146
- if (basename6) {
94257
+ const basename7 = segments[segments.length - 1];
94258
+ if (basename7) {
94147
94259
  for (const ext of FILE_EXTENSIONS) {
94148
- const basenamePath = pathResolve3(absPath, `${basename6}${ext}`);
94260
+ const basenamePath = pathResolve3(absPath, `${basename7}${ext}`);
94149
94261
  if (isFile(basenamePath)) return basenamePath;
94150
94262
  }
94151
94263
  }
@@ -102858,14 +102970,14 @@ var CANDIDATE_PATHS = [join14(".nuxt", "components.d.ts"), join14(".nuxt", "type
102858
102970
  var AUGMENTED_MODULE = /declare\s+module\s+["'](?:vue|@vue\/runtime-core)["']\s*\{/g;
102859
102971
  var INTERFACE_DECL = /(?:^|[\s;}])(?:export\s+)?(?:declare\s+)?interface\s+([A-Za-z_$][\w$]*)\s*(?:extends\s+([^{]*?))?\s*\{/g;
102860
102972
  var MEMBER = /^\s*(?:["']([^"']+)["']|([A-Za-z_$][\w$]*))\??\s*:\s*.*typeof import\(["']([^"']+)["']\)\[["']([^"']+)["']\]/;
102861
- function matchingBlock(source, open) {
102973
+ function matchingBlock(source, open2) {
102862
102974
  let depth = 0;
102863
- for (let i2 = open; i2 < source.length; i2++) {
102975
+ for (let i2 = open2; i2 < source.length; i2++) {
102864
102976
  const ch = source[i2];
102865
102977
  if (ch === "{") depth++;
102866
102978
  else if (ch === "}") {
102867
102979
  depth--;
102868
- if (depth === 0) return { body: source.slice(open + 1, i2), end: i2 };
102980
+ if (depth === 0) return { body: source.slice(open2 + 1, i2), end: i2 };
102869
102981
  }
102870
102982
  }
102871
102983
  return null;
@@ -102874,8 +102986,8 @@ function collectInterfaces(source) {
102874
102986
  const out2 = [];
102875
102987
  INTERFACE_DECL.lastIndex = 0;
102876
102988
  for (let m = INTERFACE_DECL.exec(source); m !== null; m = INTERFACE_DECL.exec(source)) {
102877
- const open = m.index + m[0].length - 1;
102878
- const block2 = matchingBlock(source, open);
102989
+ const open2 = m.index + m[0].length - 1;
102990
+ const block2 = matchingBlock(source, open2);
102879
102991
  if (block2 === null) continue;
102880
102992
  out2.push({
102881
102993
  name: m[1],
@@ -102883,7 +102995,7 @@ function collectInterfaces(source) {
102883
102995
  body: block2.body,
102884
102996
  start: m.index
102885
102997
  });
102886
- INTERFACE_DECL.lastIndex = open + 1;
102998
+ INTERFACE_DECL.lastIndex = open2 + 1;
102887
102999
  }
102888
103000
  return out2;
102889
103001
  }
@@ -102891,8 +103003,8 @@ function augmentedRanges(source) {
102891
103003
  const out2 = [];
102892
103004
  AUGMENTED_MODULE.lastIndex = 0;
102893
103005
  for (let m = AUGMENTED_MODULE.exec(source); m !== null; m = AUGMENTED_MODULE.exec(source)) {
102894
- const open = m.index + m[0].length - 1;
102895
- const block2 = matchingBlock(source, open);
103006
+ const open2 = m.index + m[0].length - 1;
103007
+ const block2 = matchingBlock(source, open2);
102896
103008
  if (block2 === null) continue;
102897
103009
  out2.push([m.index, block2.end]);
102898
103010
  AUGMENTED_MODULE.lastIndex = block2.end;
@@ -103483,7 +103595,7 @@ function normaliseVia(v, configDir2, rebase) {
103483
103595
  // src/commands/init.ts
103484
103596
  init_adapter();
103485
103597
  import { writeFile as writeFile3, stat as stat2, readFile as readFile6 } from "node:fs/promises";
103486
- import { basename as basename5, isAbsolute as isAbsolute13, resolve as resolve12 } from "node:path";
103598
+ import { basename as basename6, isAbsolute as isAbsolute13, resolve as resolve12 } from "node:path";
103487
103599
  var DEFAULT_INCLUDE = "src/**/*.{ts,tsx,jsx,js,vue,html}";
103488
103600
  var FRAMEWORK_EXTS = {
103489
103601
  react: ["ts", "tsx", "js", "jsx"],
@@ -103586,7 +103698,7 @@ async function deriveRepoId(cwd2) {
103586
103698
  if (pkg.name) return pkg.name;
103587
103699
  } catch {
103588
103700
  }
103589
- return basename5(cwd2) || "unknown";
103701
+ return basename6(cwd2) || "unknown";
103590
103702
  }
103591
103703
 
103592
103704
  // src/cli.ts
@@ -103639,8 +103751,8 @@ Options:
103639
103751
  `;
103640
103752
  var AUTH = `component-compass auth <login|logout|status> [--host <url>]
103641
103753
 
103642
- login Device-flow browser login; stores tokens in ~/.config/component-compass/hosts.json
103643
- logout Remove stored tokens for a host
103754
+ login Device-flow browser login; stores a session in ~/.config/component-compass/hosts.json
103755
+ logout Revoke a session for a host
103644
103756
  status Show the signed-in identity
103645
103757
  `;
103646
103758
  var REGISTRY = { scan: SCAN, init: INIT, auth: AUTH };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@component-compass/cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "Open-source CLI for measuring design-system component usage across multi-framework consumer repos.",
5
5
  "license": "MIT",
6
6
  "type": "module",