chofex-cli 0.1.146 → 0.1.148

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/index.js +165 -53
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -58828,6 +58828,39 @@ var ChallengeCatalogSchema = exports_Schema.Struct({
58828
58828
  });
58829
58829
 
58830
58830
  // ../../packages/registration-contract/src/index.ts
58831
+ var campaignAttributionHandoffHeader = "x-chofex-campaign-attribution";
58832
+ var eventUuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
58833
+ function campaignAttributionHandoffValue(handoff) {
58834
+ if (!eventUuidPattern.test(handoff.landingId))
58835
+ return;
58836
+ if (!Number.isSafeInteger(handoff.capturedAt) || handoff.capturedAt < 0)
58837
+ return;
58838
+ return `${handoff.landingId}.${handoff.capturedAt}`;
58839
+ }
58840
+ function campaignAttributionHandoffFromValue(value3) {
58841
+ if (!value3)
58842
+ return;
58843
+ const separator = value3.lastIndexOf(".");
58844
+ if (separator < 0)
58845
+ return;
58846
+ const landingId = value3.slice(0, separator);
58847
+ const capturedAt = Number(value3.slice(separator + 1));
58848
+ const handoff = { capturedAt, landingId };
58849
+ if (campaignAttributionHandoffValue(handoff) !== value3)
58850
+ return;
58851
+ return handoff;
58852
+ }
58853
+ function campaignAttributionFromOAuthState(expectedState, returnedState) {
58854
+ if (returnedState === expectedState)
58855
+ return { valid: true };
58856
+ const prefix = `${expectedState}.`;
58857
+ if (!returnedState?.startsWith(prefix))
58858
+ return { valid: false };
58859
+ const handoff = campaignAttributionHandoffFromValue(returnedState.slice(prefix.length));
58860
+ if (!handoff)
58861
+ return { valid: false };
58862
+ return { valid: true, handoff };
58863
+ }
58831
58864
  var nonBlank = (maximum) => exports_Schema.Trim.pipe(exports_Schema.check(exports_Schema.isMinLength(1), exports_Schema.isMaxLength(maximum)));
58832
58865
  var optionalText = (maximum) => exports_Schema.optional(nonBlank(maximum));
58833
58866
  var nullableOptionalText = (maximum) => exports_Schema.optional(exports_Schema.NullOr(nonBlank(maximum)));
@@ -59140,11 +59173,13 @@ import { createServer } from "node:http";
59140
59173
 
59141
59174
  // src/config.ts
59142
59175
  var oauthIssuer = process.env.CHOFEX_OAUTH_ISSUER ?? "https://clerk.hacktheandes.com";
59176
+ var publicSiteUrl = process.env.CHOFEX_PUBLIC_SITE_URL ?? "https://hacktheandes.com";
59143
59177
  var config = {
59144
59178
  apiUrl: process.env.CHOFEX_API_URL ?? "https://hacktheandes.com",
59145
- publicSiteUrl: process.env.CHOFEX_PUBLIC_SITE_URL ?? "https://hacktheandes.com",
59179
+ publicSiteUrl,
59146
59180
  oauthClientId: process.env.CHOFEX_OAUTH_CLIENT_ID ?? "HddO78wKpq5PFIEl",
59147
59181
  authorizationUrl: `${oauthIssuer}/oauth/authorize`,
59182
+ authorizationBridgeUrl: `${publicSiteUrl}/api/v1/oauth/authorize`,
59148
59183
  tokenUrl: `${oauthIssuer}/oauth/token`,
59149
59184
  revocationUrl: `${oauthIssuer}/oauth/token/revoke`
59150
59185
  };
@@ -59304,7 +59339,7 @@ var tokenEndpointError = (status, body) => {
59304
59339
  const detail = description ? `: ${description}` : "";
59305
59340
  return new Error(`Clerk OAuth token endpoint returned ${status}${detail}`);
59306
59341
  };
59307
- var credentialsFromToken = (body, previousRefreshToken) => {
59342
+ var credentialsFromToken = (body, previousRefreshToken, campaignAttribution) => {
59308
59343
  if (typeof body.access_token !== "string") {
59309
59344
  let detail = "";
59310
59345
  if (typeof body.error_description === "string") {
@@ -59322,6 +59357,7 @@ var credentialsFromToken = (body, previousRefreshToken) => {
59322
59357
  }
59323
59358
  return {
59324
59359
  accessToken: body.access_token,
59360
+ campaignAttribution,
59325
59361
  refreshToken,
59326
59362
  expiresAt
59327
59363
  };
@@ -59360,20 +59396,26 @@ var refresh = async (credentials) => {
59360
59396
  refresh_token: credentials.refreshToken,
59361
59397
  client_id: config.oauthClientId
59362
59398
  });
59363
- const updated = credentialsFromToken(body, credentials.refreshToken);
59399
+ const updated = credentialsFromToken(body, credentials.refreshToken, credentials.campaignAttribution);
59364
59400
  await saveCredentials(updated);
59365
59401
  return updated;
59366
59402
  };
59367
- var accessToken = async (forceRefresh = false) => {
59368
- if (process.env.CHOFEX_TOKEN)
59369
- return process.env.CHOFEX_TOKEN;
59403
+ var authentication = async (forceRefresh = false) => {
59404
+ if (process.env.CHOFEX_TOKEN) {
59405
+ return { accessToken: process.env.CHOFEX_TOKEN };
59406
+ }
59370
59407
  const credentials = await readCredentials();
59371
59408
  if (!credentials)
59372
59409
  throw new Error("Not logged in. Run `chofex login`.");
59373
59410
  if (forceRefresh || credentials.expiresAt !== undefined && credentials.expiresAt <= Date.now() + 30000) {
59374
- return (await refresh(credentials)).accessToken;
59411
+ return await refresh(credentials);
59375
59412
  }
59376
- return credentials.accessToken;
59413
+ return credentials;
59414
+ };
59415
+ var bridgedAuthorizationUrl = (bridgeUrl, authorizationUrl) => {
59416
+ const bridge = new URL(bridgeUrl);
59417
+ bridge.search = new URL(authorizationUrl).search;
59418
+ return bridge.toString();
59377
59419
  };
59378
59420
  var browserCommand = (url2, platform = process.platform) => {
59379
59421
  if (platform === "darwin")
@@ -59412,7 +59454,8 @@ var login = async () => {
59412
59454
  }
59413
59455
  const error2 = url2.searchParams.get("error");
59414
59456
  const authorizationCode = url2.searchParams.get("code");
59415
- if (url2.searchParams.get("state") !== state || error2 || !authorizationCode) {
59457
+ const attributionState = campaignAttributionFromOAuthState(state, url2.searchParams.get("state"));
59458
+ if (!attributionState.valid || error2 || !authorizationCode) {
59416
59459
  response.writeHead(400, { "content-type": "text/plain" }).end("Chofex login failed. You can close this window.");
59417
59460
  let message = "Invalid OAuth callback";
59418
59461
  if (error2)
@@ -59421,7 +59464,10 @@ var login = async () => {
59421
59464
  return;
59422
59465
  }
59423
59466
  response.writeHead(200, { "content-type": "text/plain" }).end("Chofex login complete. You can close this window.");
59424
- finish(authorizationCode);
59467
+ finish({
59468
+ authorizationCode,
59469
+ campaignAttribution: attributionState.handoff
59470
+ });
59425
59471
  });
59426
59472
  await new Promise((resolve4, reject) => {
59427
59473
  server.once("error", reject);
@@ -59448,23 +59494,26 @@ var login = async () => {
59448
59494
  server.close();
59449
59495
  throw error2;
59450
59496
  }
59497
+ const browserUrl = bridgedAuthorizationUrl(config.authorizationBridgeUrl, authorizationUrl.toString());
59451
59498
  console.error(`Sign in here (waiting up to five minutes):
59452
59499
 
59453
- ${authorizationUrl}
59500
+ ${browserUrl}
59454
59501
 
59455
59502
  If the browser did not open automatically, open that URL yourself.`);
59456
- openBrowser(authorizationUrl.toString());
59503
+ openBrowser(browserUrl);
59457
59504
  const timeout3 = setTimeout(() => fail11(new Error("Login timed out after five minutes")), 300000);
59458
59505
  try {
59459
- const authorizationCode = await code;
59506
+ const callback4 = await code;
59460
59507
  const body = await tokenRequest({
59461
59508
  grant_type: "authorization_code",
59462
59509
  client_id: config.oauthClientId,
59463
- code: authorizationCode,
59510
+ code: callback4.authorizationCode,
59464
59511
  redirect_uri: redirectUri,
59465
59512
  code_verifier: verifier
59466
59513
  });
59467
- await saveCredentials(credentialsFromToken(body));
59514
+ const credentials = credentialsFromToken(body, undefined, callback4.campaignAttribution);
59515
+ await saveCredentials(credentials);
59516
+ return credentials.accessToken;
59468
59517
  } finally {
59469
59518
  clearTimeout(timeout3);
59470
59519
  server.close();
@@ -59535,20 +59584,27 @@ var exitCodeFor = (error2) => {
59535
59584
 
59536
59585
  // src/api-client.ts
59537
59586
  var endpoint = (apiUrl, path4) => `${apiUrl.replace(/\/$/, "")}${path4}`;
59538
- var resolveAccessToken = (suppliedToken, forceRefresh = false) => {
59539
- if (suppliedToken)
59540
- return exports_Effect.succeed(suppliedToken);
59587
+ var resolveAuthentication = (suppliedToken, campaignAttribution, forceRefresh = false) => {
59588
+ if (suppliedToken) {
59589
+ return exports_Effect.succeed({ accessToken: suppliedToken, campaignAttribution });
59590
+ }
59541
59591
  return exports_Effect.tryPromise({
59542
- try: () => accessToken(forceRefresh),
59592
+ try: () => authentication(forceRefresh),
59543
59593
  catch: (error2) => cliError("AUTHENTICATION_REQUIRED", String(error2), false)
59544
59594
  });
59545
59595
  };
59546
- var sendRequest = (options, path4, init, token) => exports_Effect.tryPromise({
59596
+ var sendRequest = (options, path4, init, credentials) => exports_Effect.tryPromise({
59547
59597
  try: () => {
59548
59598
  const headers = new Headers(init.headers);
59549
59599
  headers.set("accept", "application/json");
59550
- if (token)
59551
- headers.set("authorization", `Bearer ${token}`);
59600
+ if (credentials) {
59601
+ headers.set("authorization", `Bearer ${credentials.accessToken}`);
59602
+ if (credentials.campaignAttribution) {
59603
+ const handoff = campaignAttributionHandoffValue(credentials.campaignAttribution);
59604
+ if (handoff)
59605
+ headers.set(campaignAttributionHandoffHeader, handoff);
59606
+ }
59607
+ }
59552
59608
  headers.set("x-request-id", crypto.randomUUID());
59553
59609
  if (init.body !== undefined) {
59554
59610
  headers.set("content-type", "application/json");
@@ -59577,11 +59633,11 @@ var decodeHttpBody = exports_Effect.fn("decodeHttpBody")(function* (response, de
59577
59633
  });
59578
59634
  var request3 = exports_Effect.fn("apiRequest")(function* (options, path4, init, decodeResponse) {
59579
59635
  const suppliedToken = options.token ?? process.env.CHOFEX_TOKEN;
59580
- let token = yield* resolveAccessToken(suppliedToken);
59581
- let response = yield* sendRequest(options, path4, init, token);
59636
+ let credentials = yield* resolveAuthentication(suppliedToken, options.campaignAttribution);
59637
+ let response = yield* sendRequest(options, path4, init, credentials);
59582
59638
  if (response.status === 401 && !suppliedToken) {
59583
- token = yield* resolveAccessToken(undefined, true);
59584
- response = yield* sendRequest(options, path4, init, token);
59639
+ credentials = yield* resolveAuthentication(undefined, undefined, true);
59640
+ response = yield* sendRequest(options, path4, init, credentials);
59585
59641
  }
59586
59642
  return yield* decodeHttpBody(response, decodeResponse);
59587
59643
  });
@@ -62460,7 +62516,7 @@ var uploadPicture = exports_Effect.fn("uploadPicture")(function* (options, path4
62460
62516
  import { spawn as spawn3 } from "node:child_process";
62461
62517
  import { mkdtemp as mkdtemp2, rm as rm2 } from "node:fs/promises";
62462
62518
  import { tmpdir as tmpdir2 } from "node:os";
62463
- import { join as join3 } from "node:path";
62519
+ import { dirname as dirname2, join as join3 } from "node:path";
62464
62520
  var cliPackageName = "chofex-cli";
62465
62521
  var upgradeVersion = "latest";
62466
62522
  var npmUpgradeArguments = (cacheDirectory) => [
@@ -62471,31 +62527,81 @@ var npmUpgradeArguments = (cacheDirectory) => [
62471
62527
  "--prefer-online",
62472
62528
  `--cache=${cacheDirectory}`
62473
62529
  ];
62474
- var runNpm = (arguments_) => new Promise((resolve4, reject) => {
62475
- const executable = process.platform === "win32" ? "npm.cmd" : "npm";
62530
+ var runProcess = (executable, arguments_, input) => new Promise((resolve4, reject) => {
62531
+ const stdin = input === undefined ? "ignore" : "pipe";
62476
62532
  const child = spawn3(executable, arguments_, {
62477
- stdio: ["ignore", "ignore", "pipe"]
62533
+ stdio: [stdin, "ignore", "pipe"]
62478
62534
  });
62479
62535
  let stderr = "";
62480
- child.stderr.setEncoding("utf8");
62481
- child.stderr.on("data", (chunk) => {
62536
+ const stderrStream = child.stderr;
62537
+ if (stderrStream === null) {
62538
+ child.kill();
62539
+ reject(new Error(`Could not capture stderr from ${executable}`));
62540
+ return;
62541
+ }
62542
+ stderrStream.setEncoding("utf8");
62543
+ stderrStream.on("data", (chunk) => {
62482
62544
  stderr += chunk;
62483
62545
  });
62484
62546
  child.on("error", reject);
62485
62547
  child.on("close", (exitCode) => {
62486
62548
  resolve4({ exitCode: exitCode ?? 1, stderr: stderr.trim() });
62487
62549
  });
62550
+ if (input !== undefined) {
62551
+ const stdinStream = child.stdin;
62552
+ if (stdinStream === null) {
62553
+ child.kill();
62554
+ reject(new Error(`Could not send input to ${executable}`));
62555
+ return;
62556
+ }
62557
+ stdinStream.end(input);
62558
+ }
62488
62559
  });
62489
- var upgradeCli = async (runner = runNpm) => {
62560
+ var runNpm = (arguments_) => {
62561
+ const executable = process.platform === "win32" ? "npm.cmd" : "npm";
62562
+ return runProcess(executable, arguments_);
62563
+ };
62564
+ var installerUrl = "https://hacktheandes.com/install";
62565
+ var runInstaller = async (installDirectory) => {
62566
+ const response = await fetch(installerUrl);
62567
+ if (!response.ok) {
62568
+ throw new Error(`installer download returned HTTP ${response.status}`);
62569
+ }
62570
+ const script = await response.text();
62571
+ const arguments_ = [
62572
+ "-s",
62573
+ "--",
62574
+ "--install-dir",
62575
+ installDirectory,
62576
+ "--no-modify-path"
62577
+ ];
62578
+ if (process.platform === "win32") {
62579
+ arguments_.push("--defer-until-pid", String(process.pid));
62580
+ }
62581
+ return runProcess("bash", arguments_, script);
62582
+ };
62583
+ var isStandaloneExecutable = () => typeof Bun !== "undefined" && Bun.isStandaloneExecutable;
62584
+ var assertSuccessful = (result3, program) => {
62585
+ if (result3.exitCode === 0)
62586
+ return;
62587
+ let detail = "";
62588
+ if (result3.stderr)
62589
+ detail = `: ${result3.stderr}`;
62590
+ throw new Error(`${program} exited with code ${result3.exitCode}${detail}`);
62591
+ };
62592
+ var upgradeCli = async (options = {}) => {
62593
+ const standalone = options.standalone ?? isStandaloneExecutable();
62594
+ if (standalone) {
62595
+ const runner2 = options.installerRunner ?? runInstaller;
62596
+ const result3 = await runner2(dirname2(process.execPath));
62597
+ assertSuccessful(result3, "installer");
62598
+ return;
62599
+ }
62600
+ const runner = options.npmRunner ?? runNpm;
62490
62601
  const cacheDirectory = await mkdtemp2(join3(tmpdir2(), "chofex-npm-cache-"));
62491
62602
  try {
62492
62603
  const result3 = await runner(npmUpgradeArguments(cacheDirectory));
62493
- if (result3.exitCode === 0)
62494
- return;
62495
- let detail = "";
62496
- if (result3.stderr)
62497
- detail = `: ${result3.stderr}`;
62498
- throw new Error(`npm exited with code ${result3.exitCode}${detail}`);
62604
+ assertSuccessful(result3, "npm");
62499
62605
  } finally {
62500
62606
  await rm2(cacheDirectory, { recursive: true, force: true });
62501
62607
  }
@@ -62633,17 +62739,18 @@ var confirmCommand = exports_Command.make("confirm", {
62633
62739
  ]));
62634
62740
  var loginCommand = exports_Command.make("login", {}, exports_Effect.fn("loginCommand")(function* () {
62635
62741
  const options = yield* root;
62636
- const operation = exports_Effect.tryPromise({
62637
- try: async () => {
62638
- await login();
62639
- return {
62640
- version: 1,
62641
- ok: true,
62642
- requestId: crypto.randomUUID(),
62643
- data: { authenticated: true }
62644
- };
62645
- },
62646
- catch: (error2) => cliError("LOGIN_FAILED", String(error2))
62742
+ const operation = exports_Effect.gen(function* () {
62743
+ const token = yield* exports_Effect.tryPromise({
62744
+ try: () => login(),
62745
+ catch: (error2) => cliError("LOGIN_FAILED", String(error2))
62746
+ });
62747
+ yield* getCurrentUser({ apiUrl: options.apiUrl, token }).pipe(exports_Effect.catch(() => exports_Effect.succeed(undefined)));
62748
+ return {
62749
+ version: 1,
62750
+ ok: true,
62751
+ requestId: crypto.randomUUID(),
62752
+ data: { authenticated: true }
62753
+ };
62647
62754
  });
62648
62755
  yield* execute(options.output, operation, () => "Signed in successfully.");
62649
62756
  })).pipe(exports_Command.withDescription("Sign in through Clerk OAuth in your browser"));
@@ -62693,7 +62800,7 @@ var makeUpgradeCommand = (name) => exports_Command.make(name, {}, exports_Effect
62693
62800
  }
62694
62801
  };
62695
62802
  },
62696
- catch: (error2) => cliError("UPGRADE_FAILED", `npm could not update ${cliPackageName}`, false, String(error2))
62803
+ catch: (error2) => cliError("UPGRADE_FAILED", `Could not update ${cliPackageName}`, false, String(error2))
62697
62804
  });
62698
62805
  yield* execute(options.output, operation, () => `Updated ${cliPackageName} to the ${upgradeVersion} version.`);
62699
62806
  })).pipe(exports_Command.withDescription("Update chofex-cli to the latest version"));
@@ -62929,8 +63036,13 @@ ${shortcuts.flatMap((line) => wrap(line, width)).join(`
62929
63036
  };
62930
63037
 
62931
63038
  // src/index.ts
62932
- var packageMetadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
62933
- var cliVersion = packageMetadata.version;
63039
+ var readCliVersion = () => {
63040
+ if (typeof CHOFEX_VERSION !== "undefined")
63041
+ return CHOFEX_VERSION;
63042
+ const packageMetadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
63043
+ return packageMetadata.version;
63044
+ };
63045
+ var cliVersion = readCliVersion();
62934
63046
  var arguments_ = process.argv.slice(2);
62935
63047
  var jsonOutputRequested = (arguments_2) => {
62936
63048
  if (arguments_2.includes("--output=json"))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chofex-cli",
3
- "version": "0.1.146",
3
+ "version": "0.1.148",
4
4
  "description": "Command-line client for Hack the Andes",
5
5
  "type": "module",
6
6
  "bin": {