@kidd-cli/core 0.2.0 → 0.4.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 (41) hide show
  1. package/README.md +2 -3
  2. package/dist/{config-Db_sjFU-.js → config-D8e5qxLp.js} +5 -17
  3. package/dist/config-D8e5qxLp.js.map +1 -0
  4. package/dist/{create-store-D-fQpCql.js → create-store-OHdkm_Yt.js} +3 -4
  5. package/dist/{create-store-D-fQpCql.js.map → create-store-OHdkm_Yt.js.map} +1 -1
  6. package/dist/index.d.ts +36 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +297 -73
  9. package/dist/index.js.map +1 -1
  10. package/dist/lib/config.js +3 -4
  11. package/dist/lib/logger.d.ts +1 -1
  12. package/dist/lib/logger.js +1 -2
  13. package/dist/lib/logger.js.map +1 -1
  14. package/dist/lib/project.d.ts +1 -1
  15. package/dist/lib/project.d.ts.map +1 -1
  16. package/dist/lib/project.js +2 -3
  17. package/dist/lib/store.d.ts +1 -1
  18. package/dist/lib/store.js +3 -4
  19. package/dist/{logger-BkQQej8h.d.ts → logger-9j49T5da.d.ts} +1 -1
  20. package/dist/{logger-BkQQej8h.d.ts.map → logger-9j49T5da.d.ts.map} +1 -1
  21. package/dist/middleware/auth.d.ts +81 -41
  22. package/dist/middleware/auth.d.ts.map +1 -1
  23. package/dist/middleware/auth.js +287 -233
  24. package/dist/middleware/auth.js.map +1 -1
  25. package/dist/middleware/http.d.ts +1 -1
  26. package/dist/middleware/http.js +163 -4
  27. package/dist/middleware/http.js.map +1 -1
  28. package/dist/{middleware-BFBKNSPQ.js → middleware-BWnPSRWR.js} +2 -4
  29. package/dist/{middleware-BFBKNSPQ.js.map → middleware-BWnPSRWR.js.map} +1 -1
  30. package/dist/{project-DuXgjaa_.js → project-D0g84bZY.js} +4 -8
  31. package/dist/project-D0g84bZY.js.map +1 -0
  32. package/dist/{types-C0CYivzY.d.ts → types-D-BxshYM.d.ts} +1 -1
  33. package/dist/{types-C0CYivzY.d.ts.map → types-D-BxshYM.d.ts.map} +1 -1
  34. package/dist/{types-BaZ5WqVM.d.ts → types-U73X_oQ_.d.ts} +60 -10
  35. package/dist/types-U73X_oQ_.d.ts.map +1 -0
  36. package/package.json +7 -7
  37. package/dist/config-Db_sjFU-.js.map +0 -1
  38. package/dist/create-http-client-tZJWlWp1.js +0 -165
  39. package/dist/create-http-client-tZJWlWp1.js.map +0 -1
  40. package/dist/project-DuXgjaa_.js.map +0 -1
  41. 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";
6
+ import { z } from "zod";
7
7
  import { match as match$1 } from "ts-pattern";
8
8
  import { platform } from "node:os";
9
9
  import { readFileSync } from "node:fs";
10
- import { Buffer } from "node:buffer";
11
10
  import { execFile } from "node:child_process";
12
11
  import { createServer } from "node:http";
13
12
  import { parse } from "dotenv";
14
13
  import { attempt as attempt$1 } from "es-toolkit";
15
- 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, validate } = options;
1005
944
  /**
1006
945
  * Resolve the current credential from passive sources (file, env).
1007
946
  *
@@ -1021,27 +960,33 @@ 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",
1037
980
  type: "no_credential"
1038
981
  });
1039
- const [saveError] = createStore({ dirName: `.${cliName}` }).save(DEFAULT_AUTH_FILENAME, resolved);
982
+ const [validationError, validatedCredential] = await runValidation(resolveLoginValidate(loginOptions, validate), resolved);
983
+ if (validationError) return [validationError, null];
984
+ const [saveError] = createStore({ dirName: `.${cliName}` }).save(DEFAULT_AUTH_FILENAME, validatedCredential);
1040
985
  if (saveError) return authError({
1041
986
  message: `Failed to save credential: ${saveError.message}`,
1042
987
  type: "save_failed"
1043
988
  });
1044
- return ok(resolved);
989
+ return ok(validatedCredential);
1045
990
  }
1046
991
  /**
1047
992
  * Remove the stored credential from disk.
@@ -1074,15 +1019,157 @@ function createAuthContext(options) {
1074
1019
  function authError(error) {
1075
1020
  return [error, null];
1076
1021
  }
1077
-
1022
+ /**
1023
+ * Resolve the active strategies for a login attempt.
1024
+ *
1025
+ * Returns the override strategies from login options when provided,
1026
+ * otherwise falls back to the configured strategies.
1027
+ *
1028
+ * @private
1029
+ * @param loginOptions - Optional login overrides.
1030
+ * @param configured - The default configured strategies.
1031
+ * @returns The strategies to use for the login attempt.
1032
+ */
1033
+ function resolveLoginStrategies(loginOptions, configured) {
1034
+ if (loginOptions !== void 0 && loginOptions.strategies !== void 0) return loginOptions.strategies;
1035
+ return configured;
1036
+ }
1037
+ /**
1038
+ * Resolve the active validate callback for a login attempt.
1039
+ *
1040
+ * Returns the override from login options when provided,
1041
+ * otherwise falls back to the configured validate callback.
1042
+ *
1043
+ * @private
1044
+ * @param loginOptions - Optional login overrides.
1045
+ * @param configured - The default configured validate callback.
1046
+ * @returns The validate callback to use, or undefined.
1047
+ */
1048
+ function resolveLoginValidate(loginOptions, configured) {
1049
+ if (loginOptions !== void 0 && loginOptions.validate !== void 0) return loginOptions.validate;
1050
+ return configured;
1051
+ }
1052
+ /**
1053
+ * Run the validate callback against a resolved credential.
1054
+ *
1055
+ * When no validate callback is provided, returns the credential as-is.
1056
+ * When validation fails, returns the error Result.
1057
+ * When validation succeeds, returns the (possibly transformed) credential.
1058
+ *
1059
+ * @private
1060
+ * @param validateFn - The validate callback, or undefined.
1061
+ * @param credential - The resolved credential to validate.
1062
+ * @returns A Result with the validated credential or an AuthError.
1063
+ */
1064
+ async function runValidation(validateFn, credential) {
1065
+ if (validateFn === void 0) return ok(credential);
1066
+ const [validationError, validatedCredential] = await validateFn(credential);
1067
+ if (validationError) return authError({
1068
+ message: validationError.message,
1069
+ type: "validation_failed"
1070
+ });
1071
+ return ok(validatedCredential);
1072
+ }
1073
+ //#endregion
1074
+ //#region src/middleware/http/build-auth-headers.ts
1075
+ /**
1076
+ * Convert auth credentials into HTTP headers.
1077
+ *
1078
+ * Uses exhaustive pattern matching to map each credential variant to
1079
+ * the appropriate header format.
1080
+ *
1081
+ * @module
1082
+ */
1083
+ /**
1084
+ * Convert an auth credential into HTTP headers.
1085
+ *
1086
+ * @param credential - The credential to convert.
1087
+ * @returns A record of header name to header value.
1088
+ */
1089
+ function buildAuthHeaders(credential) {
1090
+ 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();
1091
+ }
1092
+ //#endregion
1093
+ //#region src/middleware/auth/headers.ts
1094
+ /**
1095
+ * Create a function that resolves auth credentials from `ctx.auth` into HTTP headers.
1096
+ *
1097
+ * The returned function reads `ctx.auth.credential()` and converts the credential
1098
+ * into the appropriate header format using `buildAuthHeaders()`. Returns an empty
1099
+ * record when no auth middleware is present or no credential exists.
1100
+ *
1101
+ * @returns A function that takes a Context and returns auth headers.
1102
+ */
1103
+ function createAuthHeaders() {
1104
+ return function resolveHeaders(ctx) {
1105
+ if (!("auth" in ctx)) return {};
1106
+ const credential = ctx.auth.credential();
1107
+ if (credential === null) return {};
1108
+ return buildAuthHeaders(credential);
1109
+ };
1110
+ }
1111
+ //#endregion
1112
+ //#region src/middleware/auth/require.ts
1113
+ /**
1114
+ * Enforcement gate middleware that requires authentication.
1115
+ *
1116
+ * @module
1117
+ */
1118
+ const DEFAULT_MESSAGE = "Authentication required.";
1119
+ /**
1120
+ * Create an enforcement middleware that gates on authentication.
1121
+ *
1122
+ * When `ctx.auth.authenticated()` returns true, the middleware calls
1123
+ * `next()`. When not authenticated, it calls `ctx.fail()` with the
1124
+ * provided (or default) message. When `ctx.auth` is absent (auth
1125
+ * middleware not configured), it calls `ctx.fail()` with an
1126
+ * `AUTH_MIDDLEWARE_MISSING` code.
1127
+ *
1128
+ * @param options - Optional configuration for the require gate.
1129
+ * @returns A Middleware that enforces authentication.
1130
+ */
1131
+ function createAuthRequire(options) {
1132
+ const message = resolveMessage(options);
1133
+ return middleware((ctx, next) => {
1134
+ if (!hasProperty(ctx, "auth")) ctx.fail("auth.require() must run after auth() middleware", { code: "AUTH_MIDDLEWARE_MISSING" });
1135
+ if (!ctx.auth.authenticated()) ctx.fail(message, { code: "AUTH_REQUIRED" });
1136
+ return next();
1137
+ });
1138
+ }
1139
+ /**
1140
+ * Runtime property check that avoids TypeScript's `in` narrowing.
1141
+ *
1142
+ * The `Context` interface declares `auth` via module augmentation,
1143
+ * so `!('auth' in ctx)` narrows to `never`. This function uses an
1144
+ * `unknown` cast to bypass the narrowing and perform a pure runtime
1145
+ * check.
1146
+ *
1147
+ * @private
1148
+ * @param obj - The object to inspect.
1149
+ * @param key - The property name to check.
1150
+ * @returns True when the property exists on the object.
1151
+ */
1152
+ function hasProperty(obj, key) {
1153
+ return typeof obj === "object" && obj !== null && key in obj;
1154
+ }
1155
+ /**
1156
+ * Resolve the failure message from optional require options.
1157
+ *
1158
+ * @private
1159
+ * @param options - Optional require gate options.
1160
+ * @returns The resolved message string.
1161
+ */
1162
+ function resolveMessage(options) {
1163
+ if (options !== void 0 && options.message !== void 0) return options.message;
1164
+ return DEFAULT_MESSAGE;
1165
+ }
1078
1166
  //#endregion
1079
1167
  //#region src/middleware/auth/auth.ts
1080
1168
  /**
1081
- * Auth middleware factory with resolver builder functions.
1169
+ * Auth middleware factory with strategy builder functions.
1082
1170
  *
1083
1171
  * 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.
1172
+ * and run interactive authentication.
1086
1173
  *
1087
1174
  * @module
1088
1175
  */
@@ -1095,44 +1182,32 @@ function authError(error) {
1095
1182
  * 2. Dotenv — `.env` file (when configured)
1096
1183
  * 3. Env — `CLI_NAME_TOKEN`
1097
1184
  *
1098
- * Interactive resolvers (OAuth, prompt, custom) only run when the
1185
+ * Interactive strategies (OAuth, device-code, token, custom) only run when the
1099
1186
  * command handler explicitly calls `ctx.auth.login()`.
1100
1187
  *
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
1188
  * @param options - Auth middleware configuration.
1106
- * @returns A Middleware that decorates ctx.auth (and optionally HTTP clients).
1189
+ * @returns A Middleware that decorates ctx.auth.
1107
1190
  */
1108
1191
  function createAuth(options) {
1109
- const { resolvers } = options;
1192
+ const { strategies, validate } = options;
1110
1193
  return middleware((ctx, next) => {
1111
1194
  const cliName = ctx.meta.name;
1112
- const authContext = createAuthContext({
1195
+ decorateContext(ctx, "auth", createAuthContext({
1113
1196
  cliName,
1114
1197
  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);
1198
+ resolveCredential: () => resolveStoredCredential(cliName, strategies),
1199
+ strategies,
1200
+ validate
1201
+ }));
1127
1202
  return next();
1128
1203
  });
1129
1204
  }
1130
1205
  /**
1131
- * Auth middleware factory with resolver builder methods.
1206
+ * Auth middleware factory with strategy builder methods.
1132
1207
  *
1133
- * Use as `auth({ resolvers: [...] })` to create middleware, or use
1208
+ * Use as `auth({ strategies: [...] })` to create middleware, or use
1134
1209
  * the builder methods (`auth.env()`, `auth.oauth()`, etc.) to construct
1135
- * resolver configs with a cleaner API.
1210
+ * strategy configs with a cleaner API.
1136
1211
  */
1137
1212
  const auth = Object.assign(createAuth, {
1138
1213
  apiKey: buildToken,
@@ -1141,142 +1216,121 @@ const auth = Object.assign(createAuth, {
1141
1216
  dotenv: buildDotenv,
1142
1217
  env: buildEnv,
1143
1218
  file: buildFile,
1219
+ headers: createAuthHeaders,
1144
1220
  oauth: buildOAuth,
1221
+ require: createAuthRequire,
1145
1222
  token: buildToken
1146
1223
  });
1147
1224
  /**
1148
- * Build an env resolver config.
1225
+ * Build an env strategy config.
1149
1226
  *
1150
1227
  * @private
1151
- * @param options - Optional env resolver options.
1228
+ * @param options - Optional env strategy options.
1152
1229
  * @returns An EnvSourceConfig with `source: 'env'`.
1153
1230
  */
1154
1231
  function buildEnv(options) {
1155
1232
  return {
1156
- source: "env",
1157
- ...options
1233
+ ...options,
1234
+ source: "env"
1158
1235
  };
1159
1236
  }
1160
1237
  /**
1161
- * Build a dotenv resolver config.
1238
+ * Build a dotenv strategy config.
1162
1239
  *
1163
1240
  * @private
1164
- * @param options - Optional dotenv resolver options.
1241
+ * @param options - Optional dotenv strategy options.
1165
1242
  * @returns A DotenvSourceConfig with `source: 'dotenv'`.
1166
1243
  */
1167
1244
  function buildDotenv(options) {
1168
1245
  return {
1169
- source: "dotenv",
1170
- ...options
1246
+ ...options,
1247
+ source: "dotenv"
1171
1248
  };
1172
1249
  }
1173
1250
  /**
1174
- * Build a file resolver config.
1251
+ * Build a file strategy config.
1175
1252
  *
1176
1253
  * @private
1177
- * @param options - Optional file resolver options.
1254
+ * @param options - Optional file strategy options.
1178
1255
  * @returns A FileSourceConfig with `source: 'file'`.
1179
1256
  */
1180
1257
  function buildFile(options) {
1181
1258
  return {
1182
- source: "file",
1183
- ...options
1259
+ ...options,
1260
+ source: "file"
1184
1261
  };
1185
1262
  }
1186
1263
  /**
1187
- * Build an OAuth resolver config.
1264
+ * Build an OAuth strategy config.
1188
1265
  *
1189
1266
  * @private
1190
- * @param options - OAuth resolver options (clientId, authUrl, tokenUrl required).
1267
+ * @param options - OAuth strategy options (clientId, authUrl, tokenUrl required).
1191
1268
  * @returns An OAuthSourceConfig with `source: 'oauth'`.
1192
1269
  */
1193
1270
  function buildOAuth(options) {
1194
1271
  return {
1195
- source: "oauth",
1196
- ...options
1272
+ ...options,
1273
+ source: "oauth"
1197
1274
  };
1198
1275
  }
1199
1276
  /**
1200
- * Build a device code resolver config.
1277
+ * Build a device code strategy config.
1201
1278
  *
1202
1279
  * @private
1203
- * @param options - Device code resolver options (clientId, deviceAuthUrl, tokenUrl required).
1280
+ * @param options - Device code strategy options (clientId, deviceAuthUrl, tokenUrl required).
1204
1281
  * @returns A DeviceCodeSourceConfig with `source: 'device-code'`.
1205
1282
  */
1206
1283
  function buildDeviceCode(options) {
1207
1284
  return {
1208
- source: "device-code",
1209
- ...options
1285
+ ...options,
1286
+ source: "device-code"
1210
1287
  };
1211
1288
  }
1212
1289
  /**
1213
- * Build a token resolver config.
1290
+ * Build a token strategy config.
1214
1291
  *
1215
1292
  * Prompts the user for a token interactively. Aliased as `auth.apiKey()`.
1216
1293
  *
1217
1294
  * @private
1218
- * @param options - Optional token resolver options.
1295
+ * @param options - Optional token strategy options.
1219
1296
  * @returns A TokenSourceConfig with `source: 'token'`.
1220
1297
  */
1221
1298
  function buildToken(options) {
1222
1299
  return {
1223
- source: "token",
1224
- ...options
1300
+ ...options,
1301
+ source: "token"
1225
1302
  };
1226
1303
  }
1227
1304
  /**
1228
- * Build a custom resolver config from a resolver function.
1305
+ * Build a custom strategy config from a strategy function.
1229
1306
  *
1230
1307
  * @private
1231
- * @param resolver - The custom resolver function.
1308
+ * @param fn - The custom strategy function.
1232
1309
  * @returns A CustomSourceConfig with `source: 'custom'`.
1233
1310
  */
1234
- function buildCustom(resolver) {
1311
+ function buildCustom(fn) {
1235
1312
  return {
1236
- resolver,
1313
+ resolver: fn,
1237
1314
  source: "custom"
1238
1315
  };
1239
1316
  }
1240
1317
  /**
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
1318
  * Attempt to resolve a credential from stored (non-interactive) sources.
1265
1319
  *
1266
1320
  * Checks the file store first, then dotenv, then falls back to the
1267
- * environment variable. Scans the resolver list for `file`, `dotenv`,
1321
+ * environment variable. Scans the strategy list for `file`, `dotenv`,
1268
1322
  * and `env` source configs to respect user-configured overrides
1269
1323
  * (e.g. a custom `tokenVar`, `dirName`, or dotenv `path`).
1270
1324
  *
1271
1325
  * @private
1272
1326
  * @param cliName - The CLI name, used to derive paths and env var names.
1273
- * @param resolvers - The configured resolver list for extracting overrides.
1327
+ * @param strategies - The configured strategy list for extracting overrides.
1274
1328
  * @returns The resolved credential, or null.
1275
1329
  */
1276
- function resolveStoredCredential(cliName, resolvers) {
1277
- const fileConfig = findResolverBySource(resolvers, "file");
1278
- const dotenvConfig = findResolverBySource(resolvers, "dotenv");
1279
- const envConfig = findResolverBySource(resolvers, "env");
1330
+ function resolveStoredCredential(cliName, strategies) {
1331
+ const fileConfig = findStrategyBySource(strategies, "file");
1332
+ const dotenvConfig = findStrategyBySource(strategies, "dotenv");
1333
+ const envConfig = findStrategyBySource(strategies, "env");
1280
1334
  const defaultTokenVar = deriveTokenVar(cliName);
1281
1335
  const fromFile = resolveFromFile({
1282
1336
  dirName: withDefault(extractProp(fileConfig, "dirName"), `.${cliName}`),
@@ -1293,15 +1347,15 @@ function resolveStoredCredential(cliName, resolvers) {
1293
1347
  return resolveFromEnv({ tokenVar: withDefault(extractProp(envConfig, "tokenVar"), defaultTokenVar) });
1294
1348
  }
1295
1349
  /**
1296
- * Find the first resolver config matching a given source type.
1350
+ * Find the first strategy config matching a given source type.
1297
1351
  *
1298
1352
  * @private
1299
- * @param resolvers - The resolver config list.
1353
+ * @param strategies - The strategy config list.
1300
1354
  * @param source - The source type to find.
1301
1355
  * @returns The matching config, or undefined.
1302
1356
  */
1303
- function findResolverBySource(resolvers, source) {
1304
- return resolvers.find((r) => r.source === source);
1357
+ function findStrategyBySource(strategies, source) {
1358
+ return strategies.find((r) => r.source === source);
1305
1359
  }
1306
1360
  /**
1307
1361
  * Safely extract a property from an optional config object.
@@ -1318,7 +1372,7 @@ function extractProp(config, key) {
1318
1372
  if (config === void 0) return;
1319
1373
  return config[key];
1320
1374
  }
1321
-
1322
1375
  //#endregion
1323
- export { auth };
1376
+ export { auth, createAuthHeaders, createAuthRequire };
1377
+
1324
1378
  //# sourceMappingURL=auth.js.map