@kidd-cli/core 0.2.0 → 0.3.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.
Files changed (40) hide show
  1. package/dist/{config-Db_sjFU-.js → config-D8e5qxLp.js} +5 -17
  2. package/dist/config-D8e5qxLp.js.map +1 -0
  3. package/dist/{create-store-D-fQpCql.js → create-store-OHdkm_Yt.js} +3 -4
  4. package/dist/{create-store-D-fQpCql.js.map → create-store-OHdkm_Yt.js.map} +1 -1
  5. package/dist/index.d.ts +31 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +75 -40
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/config.js +3 -4
  10. package/dist/lib/logger.d.ts +1 -1
  11. package/dist/lib/logger.js +1 -2
  12. package/dist/lib/logger.js.map +1 -1
  13. package/dist/lib/project.d.ts +1 -1
  14. package/dist/lib/project.d.ts.map +1 -1
  15. package/dist/lib/project.js +2 -3
  16. package/dist/lib/store.d.ts +1 -1
  17. package/dist/lib/store.js +3 -4
  18. package/dist/{logger-BkQQej8h.d.ts → logger-9j49T5da.d.ts} +1 -1
  19. package/dist/{logger-BkQQej8h.d.ts.map → logger-9j49T5da.d.ts.map} +1 -1
  20. package/dist/middleware/auth.d.ts +68 -40
  21. package/dist/middleware/auth.d.ts.map +1 -1
  22. package/dist/middleware/auth.js +245 -230
  23. package/dist/middleware/auth.js.map +1 -1
  24. package/dist/middleware/http.d.ts +1 -1
  25. package/dist/middleware/http.js +163 -4
  26. package/dist/middleware/http.js.map +1 -1
  27. package/dist/{middleware-BFBKNSPQ.js → middleware-BWnPSRWR.js} +2 -4
  28. package/dist/{middleware-BFBKNSPQ.js.map → middleware-BWnPSRWR.js.map} +1 -1
  29. package/dist/{project-DuXgjaa_.js → project-D0g84bZY.js} +4 -8
  30. package/dist/project-D0g84bZY.js.map +1 -0
  31. package/dist/{types-BaZ5WqVM.d.ts → types-CTvrsrnD.d.ts} +3 -3
  32. package/dist/types-CTvrsrnD.d.ts.map +1 -0
  33. package/dist/{types-C0CYivzY.d.ts → types-D-BxshYM.d.ts} +1 -1
  34. package/dist/{types-C0CYivzY.d.ts.map → types-D-BxshYM.d.ts.map} +1 -1
  35. package/package.json +7 -7
  36. package/dist/config-Db_sjFU-.js.map +0 -1
  37. package/dist/create-http-client-tZJWlWp1.js +0 -165
  38. package/dist/create-http-client-tZJWlWp1.js.map +0 -1
  39. package/dist/project-DuXgjaa_.js.map +0 -1
  40. package/dist/types-BaZ5WqVM.d.ts.map +0 -1
@@ -1,40 +1,18 @@
1
- import { n as decorateContext, t as middleware } from "../middleware-BFBKNSPQ.js";
2
- import "../project-DuXgjaa_.js";
3
- import { t as createStore } from "../create-store-D-fQpCql.js";
4
- import { t as createHttpClient } from "../create-http-client-tZJWlWp1.js";
1
+ import { n as decorateContext, t as middleware } from "../middleware-BWnPSRWR.js";
2
+ import "../project-D0g84bZY.js";
3
+ import { t as createStore } from "../create-store-OHdkm_Yt.js";
5
4
  import { join } from "node:path";
6
- import { ok } from "@kidd-cli/utils/fp";
5
+ import { attempt, attemptAsync, isPlainObject, match, ok } from "@kidd-cli/utils/fp";
7
6
  import { match as match$1 } from "ts-pattern";
8
7
  import { platform } from "node:os";
9
8
  import { readFileSync } from "node:fs";
10
- import { Buffer } from "node:buffer";
11
9
  import { execFile } from "node:child_process";
12
10
  import { createServer } from "node:http";
13
11
  import { parse } from "dotenv";
14
12
  import { attempt as attempt$1 } from "es-toolkit";
15
13
  import { z } from "zod";
16
14
  import { createHash, randomBytes } from "node:crypto";
17
-
18
- //#region src/middleware/http/build-auth-headers.ts
19
- /**
20
- * Convert auth credentials into HTTP headers.
21
- *
22
- * Uses exhaustive pattern matching to map each credential variant to
23
- * the appropriate header format.
24
- *
25
- * @module
26
- */
27
- /**
28
- * Convert an auth credential into HTTP headers.
29
- *
30
- * @param credential - The credential to convert.
31
- * @returns A record of header name to header value.
32
- */
33
- function buildAuthHeaders(credential) {
34
- return match$1(credential).with({ type: "bearer" }, (c) => ({ Authorization: `Bearer ${c.token}` })).with({ type: "basic" }, (c) => ({ Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString("base64")}` })).with({ type: "api-key" }, (c) => ({ [c.headerName]: c.key })).with({ type: "custom" }, (c) => ({ ...c.headers })).exhaustive();
35
- }
36
-
37
- //#endregion
15
+ import { Buffer } from "node:buffer";
38
16
  //#region src/middleware/auth/constants.ts
39
17
  /**
40
18
  * Default filename for file-based credential storage.
@@ -45,10 +23,6 @@ const DEFAULT_AUTH_FILENAME = "auth.json";
45
23
  */
46
24
  const TOKEN_VAR_SUFFIX = "_TOKEN";
47
25
  /**
48
- * Default port for the local OAuth callback server (`0` = ephemeral).
49
- */
50
- const DEFAULT_OAUTH_PORT = 0;
51
- /**
52
26
  * Default callback path for the local OAuth server.
53
27
  */
54
28
  const DEFAULT_OAUTH_CALLBACK_PATH = "/callback";
@@ -76,7 +50,6 @@ const DEFAULT_DEVICE_CODE_TIMEOUT = 3e5;
76
50
  function deriveTokenVar(cliName) {
77
51
  return `${cliName.replaceAll("-", "_").toUpperCase()}${TOKEN_VAR_SUFFIX}`;
78
52
  }
79
-
80
53
  //#endregion
81
54
  //#region src/middleware/auth/credential.ts
82
55
  /**
@@ -119,18 +92,15 @@ function createBearerCredential(token) {
119
92
  * @returns The fetch Response on success, null on failure.
120
93
  */
121
94
  async function postFormEncoded(url, params, signal) {
122
- try {
123
- return await fetch(url, {
124
- body: params.toString(),
125
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
126
- method: "POST",
127
- signal
128
- });
129
- } catch {
130
- return null;
131
- }
95
+ const [fetchError, response] = await attemptAsync(() => fetch(url, {
96
+ body: params.toString(),
97
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
98
+ method: "POST",
99
+ signal
100
+ }));
101
+ if (fetchError) return null;
102
+ return response;
132
103
  }
133
-
134
104
  //#endregion
135
105
  //#region src/middleware/auth/oauth-server.ts
136
106
  /**
@@ -245,14 +215,11 @@ function sendSuccessPage(res) {
245
215
  * @returns True when the URL uses HTTPS or HTTP on a loopback address.
246
216
  */
247
217
  function isSecureAuthUrl(url) {
248
- try {
249
- const parsed = new URL(url);
250
- if (parsed.protocol === "https:") return true;
251
- if (parsed.protocol !== "http:") return false;
252
- return isLoopbackHost(parsed.hostname);
253
- } catch {
254
- return false;
255
- }
218
+ const [error, parsed] = attempt(() => new URL(url));
219
+ if (error || !parsed) return false;
220
+ if (parsed.protocol === "https:") return true;
221
+ if (parsed.protocol !== "http:") return false;
222
+ return isLoopbackHost(parsed.hostname);
256
223
  }
257
224
  /**
258
225
  * Open a URL in the user's default browser using a platform-specific command.
@@ -269,7 +236,7 @@ function isSecureAuthUrl(url) {
269
236
  */
270
237
  function openBrowser(url) {
271
238
  if (!isHttpUrl(url)) return;
272
- const { command, args } = match$1(platform()).with("darwin", () => ({
239
+ const { command, args } = match(platform()).with("darwin", () => ({
273
240
  args: [url],
274
241
  command: "open"
275
242
  })).with("win32", () => ({
@@ -330,12 +297,9 @@ function startLocalServer(options) {
330
297
  * @returns True when the URL uses http: or https: protocol.
331
298
  */
332
299
  function isHttpUrl(url) {
333
- try {
334
- const parsed = new URL(url);
335
- return parsed.protocol === "https:" || parsed.protocol === "http:";
336
- } catch {
337
- return false;
338
- }
300
+ const [error, parsed] = attempt(() => new URL(url));
301
+ if (error || !parsed) return false;
302
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
339
303
  }
340
304
  /**
341
305
  * Check whether a hostname is a loopback address.
@@ -364,7 +328,6 @@ function isLoopbackHost(hostname) {
364
328
  function escapeCmdMeta(url) {
365
329
  return url.replaceAll(/[&|<>^]/g, "^$&");
366
330
  }
367
-
368
331
  //#endregion
369
332
  //#region src/middleware/auth/strategies/device-code.ts
370
333
  /**
@@ -432,11 +395,9 @@ async function requestDeviceAuth(options) {
432
395
  const response = await postFormEncoded(options.deviceAuthUrl, body, options.signal);
433
396
  if (!response) return null;
434
397
  if (!response.ok) return null;
435
- try {
436
- return parseDeviceAuthResponse(await response.json());
437
- } catch {
438
- return null;
439
- }
398
+ const [parseError, data] = await attemptAsync(() => response.json());
399
+ if (parseError) return null;
400
+ return parseDeviceAuthResponse(data);
440
401
  }
441
402
  /**
442
403
  * Parse a device authorization response body.
@@ -446,17 +407,16 @@ async function requestDeviceAuth(options) {
446
407
  * @returns The parsed response, or null if required fields are missing.
447
408
  */
448
409
  function parseDeviceAuthResponse(data) {
449
- if (typeof data !== "object" || data === null) return null;
450
- const record = data;
451
- if (typeof record.device_code !== "string" || record.device_code === "") return null;
452
- if (typeof record.user_code !== "string" || record.user_code === "") return null;
453
- if (typeof record.verification_uri !== "string" || record.verification_uri === "") return null;
454
- const interval = resolveServerInterval(record.interval);
410
+ if (!isPlainObject(data)) return null;
411
+ if (typeof data.device_code !== "string" || data.device_code === "") return null;
412
+ if (typeof data.user_code !== "string" || data.user_code === "") return null;
413
+ if (typeof data.verification_uri !== "string" || data.verification_uri === "") return null;
414
+ const interval = resolveServerInterval(data.interval);
455
415
  return {
456
- deviceCode: record.device_code,
416
+ deviceCode: data.device_code,
457
417
  interval,
458
- userCode: record.user_code,
459
- verificationUri: record.verification_uri
418
+ userCode: data.user_code,
419
+ verificationUri: data.verification_uri
460
420
  };
461
421
  }
462
422
  /**
@@ -471,12 +431,10 @@ function parseDeviceAuthResponse(data) {
471
431
  * @param userCode - The code the user should enter.
472
432
  */
473
433
  async function displayUserCode(prompts, verificationUri, userCode) {
474
- try {
475
- await prompts.text({
476
- defaultValue: "",
477
- message: `Open ${verificationUri} and enter code: ${userCode} (press Enter to continue)`
478
- });
479
- } catch {}
434
+ await attemptAsync(() => prompts.text({
435
+ defaultValue: "",
436
+ message: `Open ${verificationUri} and enter code: ${userCode} (press Enter to continue)`
437
+ }));
480
438
  }
481
439
  /**
482
440
  * Resolve the poll interval, preferring server-provided value.
@@ -507,7 +465,7 @@ async function pollForToken(options) {
507
465
  if (Date.now() >= options.deadline) return null;
508
466
  await sleep(options.interval);
509
467
  if (Date.now() >= options.deadline) return null;
510
- return match$1(await requestToken({
468
+ return match(await requestToken({
511
469
  clientId: options.clientId,
512
470
  deviceCode: options.deviceCode,
513
471
  signal: options.signal,
@@ -555,24 +513,19 @@ async function requestToken(options) {
555
513
  });
556
514
  const response = await postFormEncoded(options.tokenUrl, body, options.signal);
557
515
  if (!response) return { status: "error" };
558
- try {
559
- const data = await response.json();
560
- if (typeof data !== "object" || data === null) return { status: "error" };
561
- const record = data;
562
- if (response.ok && typeof record.access_token === "string" && record.access_token !== "") {
563
- if (typeof record.token_type === "string" && record.token_type.toLowerCase() !== "bearer") return { status: "error" };
564
- return {
565
- credential: createBearerCredential(record.access_token),
566
- status: "success"
567
- };
568
- }
569
- if (typeof record.error !== "string") return { status: "error" };
570
- return match$1(record.error).with("authorization_pending", () => ({ status: "pending" })).with("slow_down", () => ({ status: "slow_down" })).with("expired_token", () => ({ status: "expired" })).with("access_denied", () => ({ status: "denied" })).otherwise(() => ({ status: "error" }));
571
- } catch {
572
- return { status: "error" };
516
+ const [parseError, data] = await attemptAsync(() => response.json());
517
+ if (parseError) return { status: "error" };
518
+ if (!isPlainObject(data)) return { status: "error" };
519
+ if (response.ok && typeof data.access_token === "string" && data.access_token !== "") {
520
+ if (typeof data.token_type === "string" && data.token_type.toLowerCase() !== "bearer") return { status: "error" };
521
+ return {
522
+ credential: createBearerCredential(data.access_token),
523
+ status: "success"
524
+ };
573
525
  }
526
+ if (typeof data.error !== "string") return { status: "error" };
527
+ return match(data.error).with("authorization_pending", () => ({ status: "pending" })).with("slow_down", () => ({ status: "slow_down" })).with("expired_token", () => ({ status: "expired" })).with("access_denied", () => ({ status: "denied" })).otherwise(() => ({ status: "error" }));
574
528
  }
575
-
576
529
  //#endregion
577
530
  //#region src/middleware/auth/strategies/dotenv.ts
578
531
  /**
@@ -594,7 +547,6 @@ function resolveFromDotenv(options) {
594
547
  if (!isValidToken(token)) return null;
595
548
  return createBearerCredential(token);
596
549
  }
597
-
598
550
  //#endregion
599
551
  //#region src/middleware/auth/strategies/env.ts
600
552
  /**
@@ -608,7 +560,6 @@ function resolveFromEnv(options) {
608
560
  if (!isValidToken(token)) return null;
609
561
  return createBearerCredential(token);
610
562
  }
611
-
612
563
  //#endregion
613
564
  //#region src/middleware/auth/schema.ts
614
565
  /**
@@ -651,7 +602,6 @@ const authCredentialSchema = z.discriminatedUnion("type", [
651
602
  apiKeyCredentialSchema,
652
603
  customCredentialSchema
653
604
  ]);
654
-
655
605
  //#endregion
656
606
  //#region src/middleware/auth/strategies/file.ts
657
607
  /**
@@ -671,7 +621,6 @@ function resolveFromFile(options) {
671
621
  if (!result.success) return null;
672
622
  return result.data;
673
623
  }
674
-
675
624
  //#endregion
676
625
  //#region src/middleware/auth/strategies/oauth.ts
677
626
  /**
@@ -867,18 +816,13 @@ async function exchangeCodeForToken(options) {
867
816
  const response = await postFormEncoded(options.tokenUrl, body);
868
817
  if (!response) return null;
869
818
  if (!response.ok) return null;
870
- try {
871
- const data = await response.json();
872
- if (typeof data !== "object" || data === null) return null;
873
- const record = data;
874
- if (typeof record.access_token !== "string" || record.access_token === "") return null;
875
- if (typeof record.token_type === "string" && record.token_type.toLowerCase() !== "bearer") return null;
876
- return createBearerCredential(record.access_token);
877
- } catch {
878
- return null;
879
- }
819
+ const [parseError, data] = await attemptAsync(() => response.json());
820
+ if (parseError) return null;
821
+ if (!isPlainObject(data)) return null;
822
+ if (typeof data.access_token !== "string" || data.access_token === "") return null;
823
+ if (typeof data.token_type === "string" && data.token_type.toLowerCase() !== "bearer") return null;
824
+ return createBearerCredential(data.access_token);
880
825
  }
881
-
882
826
  //#endregion
883
827
  //#region src/middleware/auth/strategies/token.ts
884
828
  /**
@@ -893,31 +837,27 @@ async function exchangeCodeForToken(options) {
893
837
  * @returns A bearer credential on input, null on cancellation.
894
838
  */
895
839
  async function resolveFromToken(options) {
896
- try {
897
- const token = await options.prompts.password({ message: options.message });
898
- if (!isValidToken(token)) return null;
899
- return createBearerCredential(token);
900
- } catch {
901
- return null;
902
- }
840
+ const [promptError, token] = await attemptAsync(() => options.prompts.password({ message: options.message }));
841
+ if (promptError) return null;
842
+ if (!isValidToken(token)) return null;
843
+ return createBearerCredential(token);
903
844
  }
904
-
905
845
  //#endregion
906
846
  //#region src/middleware/auth/chain.ts
907
847
  const DEFAULT_PROMPT_MESSAGE = "Enter your API key";
908
848
  /**
909
- * Chain credential resolvers, returning the first non-null result.
849
+ * Chain credential strategies, returning the first non-null result.
910
850
  *
911
- * Walks the resolver list in order, dispatching each config to the
912
- * appropriate resolver function via pattern matching. Short-circuits
851
+ * Walks the strategy list in order, dispatching each config to the
852
+ * appropriate strategy function via pattern matching. Short-circuits
913
853
  * on the first successful resolution.
914
854
  *
915
- * @param options - Options with resolvers, CLI name, and prompts instance.
916
- * @returns The first resolved credential, or null if all resolvers fail.
855
+ * @param options - Options with strategies, CLI name, and prompts instance.
856
+ * @returns The first resolved credential, or null if all strategies fail.
917
857
  */
918
858
  async function runStrategyChain(options) {
919
859
  const defaultTokenVar = deriveTokenVar(options.cliName);
920
- return tryResolvers(options.resolvers, 0, defaultTokenVar, options);
860
+ return tryStrategies(options.strategies, 0, defaultTokenVar, options);
921
861
  }
922
862
  /**
923
863
  * Return the given value when defined, otherwise the fallback.
@@ -931,33 +871,33 @@ function withDefault(value, fallback) {
931
871
  return fallback;
932
872
  }
933
873
  /**
934
- * Recursively try resolvers until one returns a credential or the list is exhausted.
874
+ * Recursively try strategies until one returns a credential or the list is exhausted.
935
875
  *
936
876
  * @private
937
- * @param configs - The resolver configs.
877
+ * @param configs - The strategy configs.
938
878
  * @param index - The current index.
939
879
  * @param defaultTokenVar - The derived default token env var name.
940
880
  * @param context - The resolve options for prompts access.
941
881
  * @returns The first resolved credential, or null.
942
882
  */
943
- async function tryResolvers(configs, index, defaultTokenVar, context) {
883
+ async function tryStrategies(configs, index, defaultTokenVar, context) {
944
884
  if (index >= configs.length) return null;
945
885
  const config = configs[index];
946
886
  if (config === void 0) return null;
947
- const credential = await dispatchResolver(config, defaultTokenVar, context);
887
+ const credential = await dispatchStrategy(config, defaultTokenVar, context);
948
888
  if (credential) return credential;
949
- return tryResolvers(configs, index + 1, defaultTokenVar, context);
889
+ return tryStrategies(configs, index + 1, defaultTokenVar, context);
950
890
  }
951
891
  /**
952
- * Dispatch a single resolver config to its implementation.
892
+ * Dispatch a single strategy config to its implementation.
953
893
  *
954
894
  * @private
955
- * @param config - The resolver config to dispatch.
895
+ * @param config - The strategy config to dispatch.
956
896
  * @param defaultTokenVar - The derived default token env var name.
957
897
  * @param context - The resolve options for prompts access.
958
898
  * @returns The resolved credential, or null.
959
899
  */
960
- async function dispatchResolver(config, defaultTokenVar, context) {
900
+ async function dispatchStrategy(config, defaultTokenVar, context) {
961
901
  return match$1(config).with({ source: "env" }, (c) => resolveFromEnv({ tokenVar: withDefault(c.tokenVar, defaultTokenVar) })).with({ source: "dotenv" }, (c) => resolveFromDotenv({
962
902
  path: withDefault(c.path, join(process.cwd(), ".env")),
963
903
  tokenVar: withDefault(c.tokenVar, defaultTokenVar)
@@ -968,7 +908,7 @@ async function dispatchResolver(config, defaultTokenVar, context) {
968
908
  authUrl: c.authUrl,
969
909
  callbackPath: withDefault(c.callbackPath, DEFAULT_OAUTH_CALLBACK_PATH),
970
910
  clientId: c.clientId,
971
- port: withDefault(c.port, DEFAULT_OAUTH_PORT),
911
+ port: withDefault(c.port, 0),
972
912
  scopes: withDefault(c.scopes, []),
973
913
  timeout: withDefault(c.timeout, DEFAULT_OAUTH_TIMEOUT),
974
914
  tokenUrl: c.tokenUrl
@@ -986,7 +926,6 @@ async function dispatchResolver(config, defaultTokenVar, context) {
986
926
  prompts: context.prompts
987
927
  })).with({ source: "custom" }, (c) => c.resolver()).exhaustive();
988
928
  }
989
-
990
929
  //#endregion
991
930
  //#region src/middleware/auth/context.ts
992
931
  /**
@@ -994,14 +933,14 @@ async function dispatchResolver(config, defaultTokenVar, context) {
994
933
  *
995
934
  * No credential data is stored on the returned object. `credential()`
996
935
  * resolves passively on every call, `authenticated()` checks existence,
997
- * `login()` runs the configured interactive resolvers, saves the
936
+ * `login()` runs the configured interactive strategies, saves the
998
937
  * credential to the global file store, and `logout()` removes it.
999
938
  *
1000
939
  * @param options - Factory options.
1001
940
  * @returns An AuthContext instance.
1002
941
  */
1003
942
  function createAuthContext(options) {
1004
- const { resolvers, cliName, prompts, resolveCredential } = options;
943
+ const { strategies, cliName, prompts, resolveCredential } = options;
1005
944
  /**
1006
945
  * Resolve the current credential from passive sources (file, env).
1007
946
  *
@@ -1021,16 +960,20 @@ function createAuthContext(options) {
1021
960
  return resolveCredential() !== null;
1022
961
  }
1023
962
  /**
1024
- * Run configured resolvers interactively and persist the credential.
963
+ * Run configured strategies interactively and persist the credential.
964
+ *
965
+ * When `loginOptions.strategies` is provided, those strategies are used
966
+ * instead of the default configured list.
1025
967
  *
1026
968
  * @private
969
+ * @param loginOptions - Optional overrides for the login attempt.
1027
970
  * @returns A Result with the credential on success or an AuthError on failure.
1028
971
  */
1029
- async function login() {
972
+ async function login(loginOptions) {
1030
973
  const resolved = await runStrategyChain({
1031
974
  cliName,
1032
975
  prompts,
1033
- resolvers
976
+ strategies: resolveLoginStrategies(loginOptions, strategies)
1034
977
  });
1035
978
  if (resolved === null) return authError({
1036
979
  message: "No credential resolved from any source",
@@ -1074,15 +1017,121 @@ function createAuthContext(options) {
1074
1017
  function authError(error) {
1075
1018
  return [error, null];
1076
1019
  }
1077
-
1020
+ /**
1021
+ * Resolve the active strategies for a login attempt.
1022
+ *
1023
+ * Returns the override strategies from login options when provided,
1024
+ * otherwise falls back to the configured strategies.
1025
+ *
1026
+ * @private
1027
+ * @param loginOptions - Optional login overrides.
1028
+ * @param configured - The default configured strategies.
1029
+ * @returns The strategies to use for the login attempt.
1030
+ */
1031
+ function resolveLoginStrategies(loginOptions, configured) {
1032
+ if (loginOptions !== void 0 && loginOptions.strategies !== void 0) return loginOptions.strategies;
1033
+ return configured;
1034
+ }
1035
+ //#endregion
1036
+ //#region src/middleware/http/build-auth-headers.ts
1037
+ /**
1038
+ * Convert auth credentials into HTTP headers.
1039
+ *
1040
+ * Uses exhaustive pattern matching to map each credential variant to
1041
+ * the appropriate header format.
1042
+ *
1043
+ * @module
1044
+ */
1045
+ /**
1046
+ * Convert an auth credential into HTTP headers.
1047
+ *
1048
+ * @param credential - The credential to convert.
1049
+ * @returns A record of header name to header value.
1050
+ */
1051
+ function buildAuthHeaders(credential) {
1052
+ return match$1(credential).with({ type: "bearer" }, (c) => ({ Authorization: `Bearer ${c.token}` })).with({ type: "basic" }, (c) => ({ Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString("base64")}` })).with({ type: "api-key" }, (c) => ({ [c.headerName]: c.key })).with({ type: "custom" }, (c) => ({ ...c.headers })).exhaustive();
1053
+ }
1054
+ //#endregion
1055
+ //#region src/middleware/auth/headers.ts
1056
+ /**
1057
+ * Create a function that resolves auth credentials from `ctx.auth` into HTTP headers.
1058
+ *
1059
+ * The returned function reads `ctx.auth.credential()` and converts the credential
1060
+ * into the appropriate header format using `buildAuthHeaders()`. Returns an empty
1061
+ * record when no auth middleware is present or no credential exists.
1062
+ *
1063
+ * @returns A function that takes a Context and returns auth headers.
1064
+ */
1065
+ function createAuthHeaders() {
1066
+ return function resolveHeaders(ctx) {
1067
+ if (!("auth" in ctx)) return {};
1068
+ const credential = ctx.auth.credential();
1069
+ if (credential === null) return {};
1070
+ return buildAuthHeaders(credential);
1071
+ };
1072
+ }
1073
+ //#endregion
1074
+ //#region src/middleware/auth/require.ts
1075
+ /**
1076
+ * Enforcement gate middleware that requires authentication.
1077
+ *
1078
+ * @module
1079
+ */
1080
+ const DEFAULT_MESSAGE = "Authentication required.";
1081
+ /**
1082
+ * Create an enforcement middleware that gates on authentication.
1083
+ *
1084
+ * When `ctx.auth.authenticated()` returns true, the middleware calls
1085
+ * `next()`. When not authenticated, it calls `ctx.fail()` with the
1086
+ * provided (or default) message. When `ctx.auth` is absent (auth
1087
+ * middleware not configured), it calls `ctx.fail()` with an
1088
+ * `AUTH_MIDDLEWARE_MISSING` code.
1089
+ *
1090
+ * @param options - Optional configuration for the require gate.
1091
+ * @returns A Middleware that enforces authentication.
1092
+ */
1093
+ function createAuthRequire(options) {
1094
+ const message = resolveMessage(options);
1095
+ return middleware((ctx, next) => {
1096
+ if (!hasProperty(ctx, "auth")) ctx.fail("auth.require() must run after auth() middleware", { code: "AUTH_MIDDLEWARE_MISSING" });
1097
+ if (!ctx.auth.authenticated()) ctx.fail(message, { code: "AUTH_REQUIRED" });
1098
+ return next();
1099
+ });
1100
+ }
1101
+ /**
1102
+ * Runtime property check that avoids TypeScript's `in` narrowing.
1103
+ *
1104
+ * The `Context` interface declares `auth` via module augmentation,
1105
+ * so `!('auth' in ctx)` narrows to `never`. This function uses an
1106
+ * `unknown` cast to bypass the narrowing and perform a pure runtime
1107
+ * check.
1108
+ *
1109
+ * @private
1110
+ * @param obj - The object to inspect.
1111
+ * @param key - The property name to check.
1112
+ * @returns True when the property exists on the object.
1113
+ */
1114
+ function hasProperty(obj, key) {
1115
+ return typeof obj === "object" && obj !== null && key in obj;
1116
+ }
1117
+ /**
1118
+ * Resolve the failure message from optional require options.
1119
+ *
1120
+ * @private
1121
+ * @param options - Optional require gate options.
1122
+ * @returns The resolved message string.
1123
+ */
1124
+ function resolveMessage(options) {
1125
+ if (options !== void 0 && options.message !== void 0) return options.message;
1126
+ return DEFAULT_MESSAGE;
1127
+ }
1078
1128
  //#endregion
1079
1129
  //#region src/middleware/auth/auth.ts
1080
1130
  /**
1081
- * Auth middleware factory with resolver builder functions.
1131
+ * Auth middleware factory with strategy builder functions.
1082
1132
  *
1083
1133
  * Decorates `ctx.auth` with functions to resolve credentials on demand
1084
- * and run interactive authentication. Also supports creating authenticated
1085
- * HTTP clients via the `http` option.
1134
+ * and run interactive authentication.
1086
1135
  *
1087
1136
  * @module
1088
1137
  */
@@ -1095,44 +1144,31 @@ function authError(error) {
1095
1144
  * 2. Dotenv — `.env` file (when configured)
1096
1145
  * 3. Env — `CLI_NAME_TOKEN`
1097
1146
  *
1098
- * Interactive resolvers (OAuth, prompt, custom) only run when the
1147
+ * Interactive strategies (OAuth, device-code, token, custom) only run when the
1099
1148
  * command handler explicitly calls `ctx.auth.login()`.
1100
1149
  *
1101
- * When `options.http` is provided, the middleware also creates HTTP
1102
- * client(s) with automatic credential header injection and decorates
1103
- * them onto `ctx[namespace]`.
1104
- *
1105
1150
  * @param options - Auth middleware configuration.
1106
- * @returns A Middleware that decorates ctx.auth (and optionally HTTP clients).
1151
+ * @returns A Middleware that decorates ctx.auth.
1107
1152
  */
1108
1153
  function createAuth(options) {
1109
- const { resolvers } = options;
1154
+ const { strategies } = options;
1110
1155
  return middleware((ctx, next) => {
1111
1156
  const cliName = ctx.meta.name;
1112
- const authContext = createAuthContext({
1157
+ decorateContext(ctx, "auth", createAuthContext({
1113
1158
  cliName,
1114
1159
  prompts: ctx.prompts,
1115
- resolveCredential: () => resolveStoredCredential(cliName, resolvers),
1116
- resolvers
1117
- });
1118
- decorateContext(ctx, "auth", authContext);
1119
- if (options.http !== void 0) normalizeHttpOptions(options.http).reduce((context, httpConfig) => {
1120
- const client = createHttpClient({
1121
- baseUrl: httpConfig.baseUrl,
1122
- defaultHeaders: httpConfig.headers,
1123
- resolveHeaders: () => credentialToHeaders(authContext.credential())
1124
- });
1125
- return decorateContext(context, httpConfig.namespace, client);
1126
- }, ctx);
1160
+ resolveCredential: () => resolveStoredCredential(cliName, strategies),
1161
+ strategies
1162
+ }));
1127
1163
  return next();
1128
1164
  });
1129
1165
  }
1130
1166
  /**
1131
- * Auth middleware factory with resolver builder methods.
1167
+ * Auth middleware factory with strategy builder methods.
1132
1168
  *
1133
- * Use as `auth({ resolvers: [...] })` to create middleware, or use
1169
+ * Use as `auth({ strategies: [...] })` to create middleware, or use
1134
1170
  * the builder methods (`auth.env()`, `auth.oauth()`, etc.) to construct
1135
- * resolver configs with a cleaner API.
1171
+ * strategy configs with a cleaner API.
1136
1172
  */
1137
1173
  const auth = Object.assign(createAuth, {
1138
1174
  apiKey: buildToken,
@@ -1141,142 +1177,121 @@ const auth = Object.assign(createAuth, {
1141
1177
  dotenv: buildDotenv,
1142
1178
  env: buildEnv,
1143
1179
  file: buildFile,
1180
+ headers: createAuthHeaders,
1144
1181
  oauth: buildOAuth,
1182
+ require: createAuthRequire,
1145
1183
  token: buildToken
1146
1184
  });
1147
1185
  /**
1148
- * Build an env resolver config.
1186
+ * Build an env strategy config.
1149
1187
  *
1150
1188
  * @private
1151
- * @param options - Optional env resolver options.
1189
+ * @param options - Optional env strategy options.
1152
1190
  * @returns An EnvSourceConfig with `source: 'env'`.
1153
1191
  */
1154
1192
  function buildEnv(options) {
1155
1193
  return {
1156
- source: "env",
1157
- ...options
1194
+ ...options,
1195
+ source: "env"
1158
1196
  };
1159
1197
  }
1160
1198
  /**
1161
- * Build a dotenv resolver config.
1199
+ * Build a dotenv strategy config.
1162
1200
  *
1163
1201
  * @private
1164
- * @param options - Optional dotenv resolver options.
1202
+ * @param options - Optional dotenv strategy options.
1165
1203
  * @returns A DotenvSourceConfig with `source: 'dotenv'`.
1166
1204
  */
1167
1205
  function buildDotenv(options) {
1168
1206
  return {
1169
- source: "dotenv",
1170
- ...options
1207
+ ...options,
1208
+ source: "dotenv"
1171
1209
  };
1172
1210
  }
1173
1211
  /**
1174
- * Build a file resolver config.
1212
+ * Build a file strategy config.
1175
1213
  *
1176
1214
  * @private
1177
- * @param options - Optional file resolver options.
1215
+ * @param options - Optional file strategy options.
1178
1216
  * @returns A FileSourceConfig with `source: 'file'`.
1179
1217
  */
1180
1218
  function buildFile(options) {
1181
1219
  return {
1182
- source: "file",
1183
- ...options
1220
+ ...options,
1221
+ source: "file"
1184
1222
  };
1185
1223
  }
1186
1224
  /**
1187
- * Build an OAuth resolver config.
1225
+ * Build an OAuth strategy config.
1188
1226
  *
1189
1227
  * @private
1190
- * @param options - OAuth resolver options (clientId, authUrl, tokenUrl required).
1228
+ * @param options - OAuth strategy options (clientId, authUrl, tokenUrl required).
1191
1229
  * @returns An OAuthSourceConfig with `source: 'oauth'`.
1192
1230
  */
1193
1231
  function buildOAuth(options) {
1194
1232
  return {
1195
- source: "oauth",
1196
- ...options
1233
+ ...options,
1234
+ source: "oauth"
1197
1235
  };
1198
1236
  }
1199
1237
  /**
1200
- * Build a device code resolver config.
1238
+ * Build a device code strategy config.
1201
1239
  *
1202
1240
  * @private
1203
- * @param options - Device code resolver options (clientId, deviceAuthUrl, tokenUrl required).
1241
+ * @param options - Device code strategy options (clientId, deviceAuthUrl, tokenUrl required).
1204
1242
  * @returns A DeviceCodeSourceConfig with `source: 'device-code'`.
1205
1243
  */
1206
1244
  function buildDeviceCode(options) {
1207
1245
  return {
1208
- source: "device-code",
1209
- ...options
1246
+ ...options,
1247
+ source: "device-code"
1210
1248
  };
1211
1249
  }
1212
1250
  /**
1213
- * Build a token resolver config.
1251
+ * Build a token strategy config.
1214
1252
  *
1215
1253
  * Prompts the user for a token interactively. Aliased as `auth.apiKey()`.
1216
1254
  *
1217
1255
  * @private
1218
- * @param options - Optional token resolver options.
1256
+ * @param options - Optional token strategy options.
1219
1257
  * @returns A TokenSourceConfig with `source: 'token'`.
1220
1258
  */
1221
1259
  function buildToken(options) {
1222
1260
  return {
1223
- source: "token",
1224
- ...options
1261
+ ...options,
1262
+ source: "token"
1225
1263
  };
1226
1264
  }
1227
1265
  /**
1228
- * Build a custom resolver config from a resolver function.
1266
+ * Build a custom strategy config from a strategy function.
1229
1267
  *
1230
1268
  * @private
1231
- * @param resolver - The custom resolver function.
1269
+ * @param fn - The custom strategy function.
1232
1270
  * @returns A CustomSourceConfig with `source: 'custom'`.
1233
1271
  */
1234
- function buildCustom(resolver) {
1272
+ function buildCustom(fn) {
1235
1273
  return {
1236
- resolver,
1274
+ resolver: fn,
1237
1275
  source: "custom"
1238
1276
  };
1239
1277
  }
1240
1278
  /**
1241
- * Normalize the `http` option into an array of configs.
1242
- *
1243
- * @private
1244
- * @param http - A single config or array of configs.
1245
- * @returns An array of AuthHttpOptions.
1246
- */
1247
- function normalizeHttpOptions(http) {
1248
- if ("baseUrl" in http) return [http];
1249
- return http;
1250
- }
1251
- /**
1252
- * Convert a credential into auth headers, returning an empty record
1253
- * when no credential is available.
1254
- *
1255
- * @private
1256
- * @param credential - The credential or null.
1257
- * @returns A record of auth headers.
1258
- */
1259
- function credentialToHeaders(credential) {
1260
- if (credential === null) return {};
1261
- return buildAuthHeaders(credential);
1262
- }
1263
- /**
1264
1279
  * Attempt to resolve a credential from stored (non-interactive) sources.
1265
1280
  *
1266
1281
  * Checks the file store first, then dotenv, then falls back to the
1267
- * environment variable. Scans the resolver list for `file`, `dotenv`,
1282
+ * environment variable. Scans the strategy list for `file`, `dotenv`,
1268
1283
  * and `env` source configs to respect user-configured overrides
1269
1284
  * (e.g. a custom `tokenVar`, `dirName`, or dotenv `path`).
1270
1285
  *
1271
1286
  * @private
1272
1287
  * @param cliName - The CLI name, used to derive paths and env var names.
1273
- * @param resolvers - The configured resolver list for extracting overrides.
1288
+ * @param strategies - The configured strategy list for extracting overrides.
1274
1289
  * @returns The resolved credential, or null.
1275
1290
  */
1276
- function resolveStoredCredential(cliName, resolvers) {
1277
- const fileConfig = findResolverBySource(resolvers, "file");
1278
- const dotenvConfig = findResolverBySource(resolvers, "dotenv");
1279
- const envConfig = findResolverBySource(resolvers, "env");
1291
+ function resolveStoredCredential(cliName, strategies) {
1292
+ const fileConfig = findStrategyBySource(strategies, "file");
1293
+ const dotenvConfig = findStrategyBySource(strategies, "dotenv");
1294
+ const envConfig = findStrategyBySource(strategies, "env");
1280
1295
  const defaultTokenVar = deriveTokenVar(cliName);
1281
1296
  const fromFile = resolveFromFile({
1282
1297
  dirName: withDefault(extractProp(fileConfig, "dirName"), `.${cliName}`),
@@ -1293,15 +1308,15 @@ function resolveStoredCredential(cliName, resolvers) {
1293
1308
  return resolveFromEnv({ tokenVar: withDefault(extractProp(envConfig, "tokenVar"), defaultTokenVar) });
1294
1309
  }
1295
1310
  /**
1296
- * Find the first resolver config matching a given source type.
1311
+ * Find the first strategy config matching a given source type.
1297
1312
  *
1298
1313
  * @private
1299
- * @param resolvers - The resolver config list.
1314
+ * @param strategies - The strategy config list.
1300
1315
  * @param source - The source type to find.
1301
1316
  * @returns The matching config, or undefined.
1302
1317
  */
1303
- function findResolverBySource(resolvers, source) {
1304
- return resolvers.find((r) => r.source === source);
1318
+ function findStrategyBySource(strategies, source) {
1319
+ return strategies.find((r) => r.source === source);
1305
1320
  }
1306
1321
  /**
1307
1322
  * Safely extract a property from an optional config object.
@@ -1318,7 +1333,7 @@ function extractProp(config, key) {
1318
1333
  if (config === void 0) return;
1319
1334
  return config[key];
1320
1335
  }
1321
-
1322
1336
  //#endregion
1323
- export { auth };
1337
+ export { auth, createAuthHeaders, createAuthRequire };
1338
+
1324
1339
  //# sourceMappingURL=auth.js.map