runtime-resolver 0.3.12 → 0.3.14

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 (67) hide show
  1. package/bin/runtime-resolver.d.ts +1 -0
  2. package/bin/runtime-resolver.js +29 -261
  3. package/cli/commands/resolve.js +239 -0
  4. package/cli/schemas/json-schema.js +12 -0
  5. package/cli/schemas/response.js +86 -0
  6. package/data/bun-defaults.js +850 -0
  7. package/data/deno-defaults.js +1526 -0
  8. package/data/node-defaults.js +4400 -0
  9. package/errors/AuthenticationError.js +35 -0
  10. package/errors/CacheError.js +32 -0
  11. package/errors/FreshnessError.js +38 -0
  12. package/errors/InvalidInputError.js +33 -0
  13. package/errors/NetworkError.js +34 -0
  14. package/errors/ParseError.js +33 -0
  15. package/errors/RateLimitError.js +39 -0
  16. package/errors/VersionNotFoundError.js +37 -0
  17. package/index.d.ts +2577 -2608
  18. package/index.js +158 -132
  19. package/layers/AutoBunCacheLive.js +47 -0
  20. package/layers/AutoDenoCacheLive.js +47 -0
  21. package/layers/AutoNodeCacheLive.js +58 -0
  22. package/layers/BunReleaseCacheLive.js +25 -0
  23. package/layers/BunResolverLive.js +85 -0
  24. package/layers/BunVersionFetcherLive.js +55 -0
  25. package/layers/DenoReleaseCacheLive.js +25 -0
  26. package/layers/DenoResolverLive.js +84 -0
  27. package/layers/DenoVersionFetcherLive.js +51 -0
  28. package/layers/FreshBunCacheLive.js +60 -0
  29. package/layers/FreshDenoCacheLive.js +60 -0
  30. package/layers/FreshNodeCacheLive.js +55 -0
  31. package/layers/GitHubAppAuth.js +58 -0
  32. package/layers/GitHubAutoAuth.js +70 -0
  33. package/layers/GitHubClientLive.js +130 -0
  34. package/layers/GitHubTokenAuth.js +50 -0
  35. package/layers/NodeReleaseCacheLive.js +77 -0
  36. package/layers/NodeResolverLive.js +107 -0
  37. package/layers/NodeScheduleFetcherLive.js +37 -0
  38. package/layers/NodeVersionFetcherLive.js +55 -0
  39. package/layers/OfflineBunCacheLive.js +40 -0
  40. package/layers/OfflineDenoCacheLive.js +40 -0
  41. package/layers/OfflineNodeCacheLive.js +39 -0
  42. package/layers/RuntimeCacheLive.js +78 -0
  43. package/lib/retry.js +27 -0
  44. package/lib/semver.js +21 -0
  45. package/package.json +57 -68
  46. package/schemas/bun-release.js +61 -0
  47. package/schemas/deno-release.js +61 -0
  48. package/schemas/github.js +74 -0
  49. package/schemas/node-release.js +130 -0
  50. package/schemas/node-schedule.js +110 -0
  51. package/schemas/node.js +68 -0
  52. package/services/BunReleaseCache.js +33 -0
  53. package/services/BunResolver.js +36 -0
  54. package/services/BunVersionFetcher.js +41 -0
  55. package/services/DenoReleaseCache.js +33 -0
  56. package/services/DenoResolver.js +36 -0
  57. package/services/DenoVersionFetcher.js +41 -0
  58. package/services/GitHubClient.js +40 -0
  59. package/services/NodeReleaseCache.js +41 -0
  60. package/services/NodeResolver.js +44 -0
  61. package/services/NodeScheduleFetcher.js +38 -0
  62. package/services/NodeVersionFetcher.js +39 -0
  63. package/services/OctokitInstance.js +44 -0
  64. package/tsdoc-metadata.json +11 -11
  65. package/612.js +0 -23
  66. package/711.js +0 -7554
  67. package/json-schema.js +0 -95
@@ -0,0 +1 @@
1
+ export { };
@@ -1,269 +1,37 @@
1
1
  #!/usr/bin/env node
2
- import { Command, Options } from "@effect/cli";
2
+ import { appIdOption, appInstallationIdOption, appPrivateKeyOption, bunDefaultOption, bunOption, denoDefaultOption, denoOption, incrementsOption, nodeDateOption, nodeDefaultOption, nodeOption, nodePhasesOption, prettyOption, resolveHandler, schemaOption, tokenOption } from "../cli/commands/resolve.js";
3
+ import { Cause, Console, Effect } from "effect";
4
+ import { Command } from "@effect/cli";
3
5
  import { NodeContext, NodeRuntime } from "@effect/platform-node";
4
- import { Cause, Console, Effect, Layer, Option } from "effect";
5
- import { readFileSync } from "node:fs";
6
- import { NodeScheduleFetcherLive, AutoDenoCacheLive, DenoVersionFetcherLive, AutoNodeCacheLive, BunResolver, NodeResolver, NodeVersionFetcherLive, NodeResolverLive, GitHubAutoAuth, BunResolverLive, BunVersionFetcherLive, GitHubClientLive, DenoResolverLive, GitHubTokenAuthFromToken, GitHubAppAuth, AutoBunCacheLive, DenoResolver } from "../711.js";
7
- const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
8
- const makeNodeLayer = (authOverride)=>{
9
- const ghLayer = authOverride ? GitHubClientLive.pipe(Layer.provide(authOverride)) : GitHubLayer;
10
- const fetchersLayer = Layer.merge(NodeVersionFetcherLive.pipe(Layer.provide(ghLayer)), NodeScheduleFetcherLive.pipe(Layer.provide(ghLayer)));
11
- return NodeResolverLive.pipe(Layer.provide(AutoNodeCacheLive.pipe(Layer.provide(fetchersLayer))));
12
- };
13
- const makeBunLayer = (authOverride)=>{
14
- const ghLayer = authOverride ? GitHubClientLive.pipe(Layer.provide(authOverride)) : GitHubLayer;
15
- return BunResolverLive.pipe(Layer.provide(AutoBunCacheLive.pipe(Layer.provide(BunVersionFetcherLive.pipe(Layer.provide(ghLayer))))));
16
- };
17
- const makeDenoLayer = (authOverride)=>{
18
- const ghLayer = authOverride ? GitHubClientLive.pipe(Layer.provide(authOverride)) : GitHubLayer;
19
- return DenoResolverLive.pipe(Layer.provide(AutoDenoCacheLive.pipe(Layer.provide(DenoVersionFetcherLive.pipe(Layer.provide(ghLayer))))));
20
- };
21
- const SCHEMA_URL = "https://raw.githubusercontent.com/spencerbeggs/runtime-resolver/main/runtime-resolver.schema.json";
22
- const nodeOption = Options.text("node").pipe(Options.optional);
23
- const bunOption = Options.text("bun").pipe(Options.optional);
24
- const denoOption = Options.text("deno").pipe(Options.optional);
25
- const nodePhasesOption = Options.text("node-phases").pipe(Options.optional);
26
- const incrementsOption = Options.text("increments").pipe(Options.optional);
27
- const nodeDefaultOption = Options.text("node-default").pipe(Options.optional);
28
- const bunDefaultOption = Options.text("bun-default").pipe(Options.optional);
29
- const denoDefaultOption = Options.text("deno-default").pipe(Options.optional);
30
- const nodeDateOption = Options.text("node-date").pipe(Options.optional);
31
- const prettyOption = Options.boolean("pretty").pipe(Options.withDefault(false));
32
- const schemaOption = Options.boolean("schema").pipe(Options.withDefault(false));
33
- const tokenOption = Options.text("token").pipe(Options.optional);
34
- const appIdOption = Options.text("app-id").pipe(Options.optional);
35
- const appPrivateKeyOption = Options.text("app-private-key").pipe(Options.optional);
36
- const appInstallationIdOption = Options.text("app-installation-id").pipe(Options.optional);
37
- const VALID_PHASES = [
38
- "current",
39
- "active-lts",
40
- "maintenance-lts",
41
- "end-of-life"
42
- ];
43
- const VALID_INCREMENTS = [
44
- "latest",
45
- "minor",
46
- "patch"
47
- ];
48
- const validatePhases = (raw)=>{
49
- const phases = raw.split(",").map((s)=>s.trim());
50
- for (const p of phases)if (!VALID_PHASES.includes(p)) throw new Error(`Invalid phase: "${p}". Valid values: ${VALID_PHASES.join(", ")}`);
51
- return phases;
52
- };
53
- const validateIncrements = (raw)=>{
54
- if (!VALID_INCREMENTS.includes(raw)) throw new Error(`Invalid increments value: "${raw}". Valid values: ${VALID_INCREMENTS.join(", ")}`);
55
- return raw;
56
- };
57
- const serializeError = (error)=>{
58
- if (error && "object" == typeof error && "_tag" in error && "message" in error) {
59
- const { _tag, message, ...rest } = error;
60
- return {
61
- _tag,
62
- message,
63
- ...rest
64
- };
65
- }
66
- return {
67
- _tag: "UnknownError",
68
- message: error instanceof Error ? error.message : String(error)
69
- };
70
- };
71
- const toSuccess = (name, result)=>[
72
- name,
73
- {
74
- ok: true,
75
- source: result.source,
76
- versions: [
77
- ...result.versions
78
- ],
79
- latest: result.latest,
80
- ...void 0 !== result.lts ? {
81
- lts: result.lts
82
- } : {},
83
- ...void 0 !== result.default ? {
84
- default: result.default
85
- } : {}
86
- }
87
- ];
88
- const toError = (name, error)=>[
89
- name,
90
- {
91
- ok: false,
92
- error: serializeError(error)
93
- }
94
- ];
95
- const resolveNode = (semverRange, phases, increments, defaultVersion, date, authOverride)=>Effect.gen(function*() {
96
- const resolver = yield* NodeResolver;
97
- const result = yield* resolver.resolve({
98
- semverRange,
99
- ...phases ? {
100
- phases
101
- } : {},
102
- ...increments ? {
103
- increments
104
- } : {},
105
- ...defaultVersion ? {
106
- defaultVersion
107
- } : {},
108
- ...date ? {
109
- date
110
- } : {}
111
- });
112
- return toSuccess("node", result);
113
- }).pipe(Effect.provide(makeNodeLayer(authOverride)), Effect.catchAll((error)=>Effect.succeed(toError("node", error))));
114
- const resolveBun = (semverRange, increments, defaultVersion, authOverride)=>Effect.gen(function*() {
115
- const resolver = yield* BunResolver;
116
- const result = yield* resolver.resolve({
117
- semverRange,
118
- ...increments ? {
119
- increments
120
- } : {},
121
- ...defaultVersion ? {
122
- defaultVersion
123
- } : {}
124
- });
125
- return toSuccess("bun", result);
126
- }).pipe(Effect.provide(makeBunLayer(authOverride)), Effect.catchAll((error)=>Effect.succeed(toError("bun", error))));
127
- const resolveDeno = (semverRange, increments, defaultVersion, authOverride)=>Effect.gen(function*() {
128
- const resolver = yield* DenoResolver;
129
- const result = yield* resolver.resolve({
130
- semverRange,
131
- ...increments ? {
132
- increments
133
- } : {},
134
- ...defaultVersion ? {
135
- defaultVersion
136
- } : {}
137
- });
138
- return toSuccess("deno", result);
139
- }).pipe(Effect.provide(makeDenoLayer(authOverride)), Effect.catchAll((error)=>Effect.succeed(toError("deno", error))));
140
- const formatResponse = (ok, results, pretty)=>{
141
- const response = {
142
- $schema: SCHEMA_URL,
143
- ok,
144
- results
145
- };
146
- return JSON.stringify(response, null, pretty ? 2 : void 0);
147
- };
148
- const resolveHandler = (args)=>Effect.gen(function*() {
149
- if (args.schema) {
150
- const resolveFlags = [
151
- args.node,
152
- args.bun,
153
- args.deno,
154
- args.nodePhases,
155
- args.increments,
156
- args.nodeDefault,
157
- args.bunDefault,
158
- args.denoDefault,
159
- args.nodeDate,
160
- args.token,
161
- args.appId,
162
- args.appPrivateKey,
163
- args.appInstallationId
164
- ];
165
- const hasResolveFlags = resolveFlags.some((f)=>Option.isSome(f));
166
- if (hasResolveFlags) return void (yield* Console.error("Error: --schema cannot be combined with resolve flags (--node, --bun, --deno, etc.)"));
167
- const { cliJsonSchema } = yield* Effect.promise(()=>import("../json-schema.js").then((m)=>m.json_schema_namespaceObject));
168
- yield* Console.log(JSON.stringify(cliJsonSchema, null, 2));
169
- return;
170
- }
171
- const hasToken = Option.isSome(args.token);
172
- const hasAppId = Option.isSome(args.appId);
173
- const hasAppPrivateKey = Option.isSome(args.appPrivateKey);
174
- const hasAppInstallationId = Option.isSome(args.appInstallationId);
175
- if (hasToken && (hasAppId || hasAppPrivateKey)) return void (yield* Console.error("Error: --token and --app-id/--app-private-key are mutually exclusive"));
176
- if (hasAppId !== hasAppPrivateKey) return void (yield* Console.error("Error: --app-id and --app-private-key must both be provided"));
177
- if (hasAppInstallationId && !hasAppId) return void (yield* Console.error("Error: --app-installation-id requires --app-id and --app-private-key"));
178
- let resolvedPrivateKey;
179
- if (hasAppPrivateKey) {
180
- const keyValue = args.appPrivateKey.value;
181
- if (keyValue.startsWith("@")) {
182
- const filePath = keyValue.slice(1);
183
- try {
184
- resolvedPrivateKey = readFileSync(filePath, "utf-8");
185
- } catch {
186
- yield* Console.error(`Error: Cannot read private key file: ${filePath}`);
187
- return;
188
- }
189
- } else resolvedPrivateKey = keyValue;
190
- }
191
- let authLayerOverride;
192
- if (hasToken) authLayerOverride = GitHubTokenAuthFromToken(args.token.value);
193
- else if (hasAppId && resolvedPrivateKey) authLayerOverride = GitHubAppAuth({
194
- appId: args.appId.value,
195
- privateKey: resolvedPrivateKey,
196
- ...hasAppInstallationId ? {
197
- installationId: Number(args.appInstallationId.value)
198
- } : {}
199
- });
200
- const hasNode = Option.isSome(args.node);
201
- const hasBun = Option.isSome(args.bun);
202
- const hasDeno = Option.isSome(args.deno);
203
- if (!hasNode && !hasBun && !hasDeno) return void (yield* Console.error("No runtime specified. Use --node, --bun, or --deno to resolve versions.\nRun with --help for usage information."));
204
- let validatedPhases;
205
- if (Option.isSome(args.nodePhases)) try {
206
- validatedPhases = validatePhases(args.nodePhases.value);
207
- } catch (e) {
208
- yield* Console.error(e.message);
209
- return;
210
- }
211
- let validatedIncrements;
212
- if (Option.isSome(args.increments)) try {
213
- validatedIncrements = validateIncrements(args.increments.value);
214
- } catch (e) {
215
- yield* Console.error(e.message);
216
- return;
217
- }
218
- let nodeDate;
219
- if (Option.isSome(args.nodeDate)) {
220
- nodeDate = new Date(args.nodeDate.value);
221
- if (Number.isNaN(nodeDate.getTime())) return void (yield* Console.error(`Invalid date: "${args.nodeDate.value}". Use ISO 8601 format (e.g. 2024-01-15).`));
222
- }
223
- const tasks = [];
224
- if (hasNode) {
225
- const nodeDefaultVersion = Option.isSome(args.nodeDefault) ? args.nodeDefault.value : void 0;
226
- tasks.push(resolveNode(args.node.value, validatedPhases, validatedIncrements, nodeDefaultVersion, nodeDate, authLayerOverride));
227
- }
228
- if (hasBun) {
229
- const bunDefaultVersion = Option.isSome(args.bunDefault) ? args.bunDefault.value : void 0;
230
- tasks.push(resolveBun(args.bun.value, validatedIncrements, bunDefaultVersion, authLayerOverride));
231
- }
232
- if (hasDeno) {
233
- const denoDefaultVersion = Option.isSome(args.denoDefault) ? args.denoDefault.value : void 0;
234
- tasks.push(resolveDeno(args.deno.value, validatedIncrements, denoDefaultVersion, authLayerOverride));
235
- }
236
- const entries = yield* Effect.all(tasks, {
237
- concurrency: "unbounded"
238
- });
239
- const results = Object.fromEntries(entries);
240
- const hasError = Object.values(results).some((r)=>!r.ok);
241
- yield* Console.log(formatResponse(!hasError, results, args.pretty));
242
- });
6
+
7
+ //#region src/cli/index.ts
8
+ /* v8 ignore start - CLI entry point requires integration testing */
243
9
  const rootCommand = Command.make("runtime-resolver", {
244
- node: nodeOption,
245
- bun: bunOption,
246
- deno: denoOption,
247
- nodePhases: nodePhasesOption,
248
- increments: incrementsOption,
249
- nodeDefault: nodeDefaultOption,
250
- bunDefault: bunDefaultOption,
251
- denoDefault: denoDefaultOption,
252
- nodeDate: nodeDateOption,
253
- pretty: prettyOption,
254
- schema: schemaOption,
255
- token: tokenOption,
256
- appId: appIdOption,
257
- appPrivateKey: appPrivateKeyOption,
258
- appInstallationId: appInstallationIdOption
10
+ node: nodeOption,
11
+ bun: bunOption,
12
+ deno: denoOption,
13
+ nodePhases: nodePhasesOption,
14
+ increments: incrementsOption,
15
+ nodeDefault: nodeDefaultOption,
16
+ bunDefault: bunDefaultOption,
17
+ denoDefault: denoDefaultOption,
18
+ nodeDate: nodeDateOption,
19
+ pretty: prettyOption,
20
+ schema: schemaOption,
21
+ token: tokenOption,
22
+ appId: appIdOption,
23
+ appPrivateKey: appPrivateKeyOption,
24
+ appInstallationId: appInstallationIdOption
259
25
  }, resolveHandler);
260
26
  const cli = Command.run(rootCommand, {
261
- name: "runtime-resolver",
262
- version: "0.3.12"
27
+ name: "runtime-resolver",
28
+ version: "0.3.14"
263
29
  });
264
- const main = Effect.suspend(()=>cli(process.argv)).pipe(Effect.provide(NodeContext.layer), Effect.catchAllCause((cause)=>{
265
- const defects = Cause.defects(cause);
266
- if (defects.length > 0) return Console.error(Cause.pretty(cause)).pipe(Effect.andThen(Effect.failCause(cause)));
267
- return Effect.failCause(cause);
30
+ const main = Effect.suspend(() => cli(process.argv)).pipe(Effect.provide(NodeContext.layer), Effect.catchAllCause((cause) => {
31
+ if (Cause.defects(cause).length > 0) return Console.error(Cause.pretty(cause)).pipe(Effect.andThen(Effect.failCause(cause)));
32
+ return Effect.failCause(cause);
268
33
  }));
269
34
  NodeRuntime.runMain(main);
35
+
36
+ //#endregion
37
+ export { };
@@ -0,0 +1,239 @@
1
+ import { AutoBunCacheLive } from "../../layers/AutoBunCacheLive.js";
2
+ import { AutoDenoCacheLive } from "../../layers/AutoDenoCacheLive.js";
3
+ import { AutoNodeCacheLive } from "../../layers/AutoNodeCacheLive.js";
4
+ import { BunResolver } from "../../services/BunResolver.js";
5
+ import { BunResolverLive } from "../../layers/BunResolverLive.js";
6
+ import { BunVersionFetcherLive } from "../../layers/BunVersionFetcherLive.js";
7
+ import { DenoResolver } from "../../services/DenoResolver.js";
8
+ import { DenoResolverLive } from "../../layers/DenoResolverLive.js";
9
+ import { DenoVersionFetcherLive } from "../../layers/DenoVersionFetcherLive.js";
10
+ import { GitHubAppAuth } from "../../layers/GitHubAppAuth.js";
11
+ import { GitHubAutoAuth } from "../../layers/GitHubAutoAuth.js";
12
+ import { GitHubClientLive } from "../../layers/GitHubClientLive.js";
13
+ import { NodeResolver } from "../../services/NodeResolver.js";
14
+ import { NodeResolverLive } from "../../layers/NodeResolverLive.js";
15
+ import { NodeScheduleFetcherLive } from "../../layers/NodeScheduleFetcherLive.js";
16
+ import { NodeVersionFetcherLive } from "../../layers/NodeVersionFetcherLive.js";
17
+ import { GitHubTokenAuthFromToken } from "../../layers/GitHubTokenAuth.js";
18
+ import { Console, Effect, Layer, Option } from "effect";
19
+ import { Options } from "@effect/cli";
20
+ import { readFileSync } from "node:fs";
21
+
22
+ //#region src/cli/commands/resolve.ts
23
+ /* v8 ignore start */
24
+ const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
25
+ const makeNodeLayer = (authOverride) => {
26
+ const ghLayer = authOverride ? GitHubClientLive.pipe(Layer.provide(authOverride)) : GitHubLayer;
27
+ const fetchersLayer = Layer.merge(NodeVersionFetcherLive.pipe(Layer.provide(ghLayer)), NodeScheduleFetcherLive.pipe(Layer.provide(ghLayer)));
28
+ return NodeResolverLive.pipe(Layer.provide(AutoNodeCacheLive.pipe(Layer.provide(fetchersLayer))));
29
+ };
30
+ const makeBunLayer = (authOverride) => {
31
+ const ghLayer = authOverride ? GitHubClientLive.pipe(Layer.provide(authOverride)) : GitHubLayer;
32
+ return BunResolverLive.pipe(Layer.provide(AutoBunCacheLive.pipe(Layer.provide(BunVersionFetcherLive.pipe(Layer.provide(ghLayer))))));
33
+ };
34
+ const makeDenoLayer = (authOverride) => {
35
+ const ghLayer = authOverride ? GitHubClientLive.pipe(Layer.provide(authOverride)) : GitHubLayer;
36
+ return DenoResolverLive.pipe(Layer.provide(AutoDenoCacheLive.pipe(Layer.provide(DenoVersionFetcherLive.pipe(Layer.provide(ghLayer))))));
37
+ };
38
+ const SCHEMA_URL = "https://raw.githubusercontent.com/spencerbeggs/runtime-resolver/main/runtime-resolver.schema.json";
39
+ const nodeOption = Options.text("node").pipe(Options.optional);
40
+ const bunOption = Options.text("bun").pipe(Options.optional);
41
+ const denoOption = Options.text("deno").pipe(Options.optional);
42
+ const nodePhasesOption = Options.text("node-phases").pipe(Options.optional);
43
+ const incrementsOption = Options.text("increments").pipe(Options.optional);
44
+ const nodeDefaultOption = Options.text("node-default").pipe(Options.optional);
45
+ const bunDefaultOption = Options.text("bun-default").pipe(Options.optional);
46
+ const denoDefaultOption = Options.text("deno-default").pipe(Options.optional);
47
+ const nodeDateOption = Options.text("node-date").pipe(Options.optional);
48
+ const prettyOption = Options.boolean("pretty").pipe(Options.withDefault(false));
49
+ const schemaOption = Options.boolean("schema").pipe(Options.withDefault(false));
50
+ const tokenOption = Options.text("token").pipe(Options.optional);
51
+ const appIdOption = Options.text("app-id").pipe(Options.optional);
52
+ const appPrivateKeyOption = Options.text("app-private-key").pipe(Options.optional);
53
+ const appInstallationIdOption = Options.text("app-installation-id").pipe(Options.optional);
54
+ const VALID_PHASES = [
55
+ "current",
56
+ "active-lts",
57
+ "maintenance-lts",
58
+ "end-of-life"
59
+ ];
60
+ const VALID_INCREMENTS = [
61
+ "latest",
62
+ "minor",
63
+ "patch"
64
+ ];
65
+ const validatePhases = (raw) => {
66
+ const phases = raw.split(",").map((s) => s.trim());
67
+ for (const p of phases) if (!VALID_PHASES.includes(p)) throw new Error(`Invalid phase: "${p}". Valid values: ${VALID_PHASES.join(", ")}`);
68
+ return phases;
69
+ };
70
+ const validateIncrements = (raw) => {
71
+ if (!VALID_INCREMENTS.includes(raw)) throw new Error(`Invalid increments value: "${raw}". Valid values: ${VALID_INCREMENTS.join(", ")}`);
72
+ return raw;
73
+ };
74
+ const serializeError = (error) => {
75
+ if (error && typeof error === "object" && "_tag" in error && "message" in error) {
76
+ const { _tag, message, ...rest } = error;
77
+ return {
78
+ _tag,
79
+ message,
80
+ ...rest
81
+ };
82
+ }
83
+ return {
84
+ _tag: "UnknownError",
85
+ message: error instanceof Error ? error.message : String(error)
86
+ };
87
+ };
88
+ const toSuccess = (name, result) => [name, {
89
+ ok: true,
90
+ source: result.source,
91
+ versions: [...result.versions],
92
+ latest: result.latest,
93
+ ...result.lts !== void 0 ? { lts: result.lts } : {},
94
+ ...result.default !== void 0 ? { default: result.default } : {}
95
+ }];
96
+ const toError = (name, error) => [name, {
97
+ ok: false,
98
+ error: serializeError(error)
99
+ }];
100
+ const resolveNode = (semverRange, phases, increments, defaultVersion, date, authOverride) => Effect.gen(function* () {
101
+ return toSuccess("node", yield* (yield* NodeResolver).resolve({
102
+ semverRange,
103
+ ...phases ? { phases } : {},
104
+ ...increments ? { increments } : {},
105
+ ...defaultVersion ? { defaultVersion } : {},
106
+ ...date ? { date } : {}
107
+ }));
108
+ }).pipe(Effect.provide(makeNodeLayer(authOverride)), Effect.catchAll((error) => Effect.succeed(toError("node", error))));
109
+ const resolveBun = (semverRange, increments, defaultVersion, authOverride) => Effect.gen(function* () {
110
+ return toSuccess("bun", yield* (yield* BunResolver).resolve({
111
+ semverRange,
112
+ ...increments ? { increments } : {},
113
+ ...defaultVersion ? { defaultVersion } : {}
114
+ }));
115
+ }).pipe(Effect.provide(makeBunLayer(authOverride)), Effect.catchAll((error) => Effect.succeed(toError("bun", error))));
116
+ const resolveDeno = (semverRange, increments, defaultVersion, authOverride) => Effect.gen(function* () {
117
+ return toSuccess("deno", yield* (yield* DenoResolver).resolve({
118
+ semverRange,
119
+ ...increments ? { increments } : {},
120
+ ...defaultVersion ? { defaultVersion } : {}
121
+ }));
122
+ }).pipe(Effect.provide(makeDenoLayer(authOverride)), Effect.catchAll((error) => Effect.succeed(toError("deno", error))));
123
+ const formatResponse = (ok, results, pretty) => {
124
+ return JSON.stringify({
125
+ $schema: SCHEMA_URL,
126
+ ok,
127
+ results
128
+ }, null, pretty ? 2 : void 0);
129
+ };
130
+ const resolveHandler = (args) => Effect.gen(function* () {
131
+ if (args.schema) {
132
+ if ([
133
+ args.node,
134
+ args.bun,
135
+ args.deno,
136
+ args.nodePhases,
137
+ args.increments,
138
+ args.nodeDefault,
139
+ args.bunDefault,
140
+ args.denoDefault,
141
+ args.nodeDate,
142
+ args.token,
143
+ args.appId,
144
+ args.appPrivateKey,
145
+ args.appInstallationId
146
+ ].some((f) => Option.isSome(f))) {
147
+ yield* Console.error("Error: --schema cannot be combined with resolve flags (--node, --bun, --deno, etc.)");
148
+ return;
149
+ }
150
+ const { cliJsonSchema } = yield* Effect.promise(() => import("../schemas/json-schema.js"));
151
+ yield* Console.log(JSON.stringify(cliJsonSchema, null, 2));
152
+ return;
153
+ }
154
+ const hasToken = Option.isSome(args.token);
155
+ const hasAppId = Option.isSome(args.appId);
156
+ const hasAppPrivateKey = Option.isSome(args.appPrivateKey);
157
+ const hasAppInstallationId = Option.isSome(args.appInstallationId);
158
+ if (hasToken && (hasAppId || hasAppPrivateKey)) {
159
+ yield* Console.error("Error: --token and --app-id/--app-private-key are mutually exclusive");
160
+ return;
161
+ }
162
+ if (hasAppId !== hasAppPrivateKey) {
163
+ yield* Console.error("Error: --app-id and --app-private-key must both be provided");
164
+ return;
165
+ }
166
+ if (hasAppInstallationId && !hasAppId) {
167
+ yield* Console.error("Error: --app-installation-id requires --app-id and --app-private-key");
168
+ return;
169
+ }
170
+ let resolvedPrivateKey;
171
+ if (hasAppPrivateKey) {
172
+ const keyValue = args.appPrivateKey.value;
173
+ if (keyValue.startsWith("@")) {
174
+ const filePath = keyValue.slice(1);
175
+ try {
176
+ resolvedPrivateKey = readFileSync(filePath, "utf-8");
177
+ } catch {
178
+ yield* Console.error(`Error: Cannot read private key file: ${filePath}`);
179
+ return;
180
+ }
181
+ } else resolvedPrivateKey = keyValue;
182
+ }
183
+ let authLayerOverride;
184
+ if (hasToken) authLayerOverride = GitHubTokenAuthFromToken(args.token.value);
185
+ else if (hasAppId && resolvedPrivateKey) authLayerOverride = GitHubAppAuth({
186
+ appId: args.appId.value,
187
+ privateKey: resolvedPrivateKey,
188
+ ...hasAppInstallationId ? { installationId: Number(args.appInstallationId.value) } : {}
189
+ });
190
+ const hasNode = Option.isSome(args.node);
191
+ const hasBun = Option.isSome(args.bun);
192
+ const hasDeno = Option.isSome(args.deno);
193
+ if (!hasNode && !hasBun && !hasDeno) {
194
+ yield* Console.error("No runtime specified. Use --node, --bun, or --deno to resolve versions.\nRun with --help for usage information.");
195
+ return;
196
+ }
197
+ let validatedPhases;
198
+ if (Option.isSome(args.nodePhases)) try {
199
+ validatedPhases = validatePhases(args.nodePhases.value);
200
+ } catch (e) {
201
+ yield* Console.error(e.message);
202
+ return;
203
+ }
204
+ let validatedIncrements;
205
+ if (Option.isSome(args.increments)) try {
206
+ validatedIncrements = validateIncrements(args.increments.value);
207
+ } catch (e) {
208
+ yield* Console.error(e.message);
209
+ return;
210
+ }
211
+ let nodeDate;
212
+ if (Option.isSome(args.nodeDate)) {
213
+ nodeDate = new Date(args.nodeDate.value);
214
+ if (Number.isNaN(nodeDate.getTime())) {
215
+ yield* Console.error(`Invalid date: "${args.nodeDate.value}". Use ISO 8601 format (e.g. 2024-01-15).`);
216
+ return;
217
+ }
218
+ }
219
+ const tasks = [];
220
+ if (hasNode) {
221
+ const nodeDefaultVersion = Option.isSome(args.nodeDefault) ? args.nodeDefault.value : void 0;
222
+ tasks.push(resolveNode(args.node.value, validatedPhases, validatedIncrements, nodeDefaultVersion, nodeDate, authLayerOverride));
223
+ }
224
+ if (hasBun) {
225
+ const bunDefaultVersion = Option.isSome(args.bunDefault) ? args.bunDefault.value : void 0;
226
+ tasks.push(resolveBun(args.bun.value, validatedIncrements, bunDefaultVersion, authLayerOverride));
227
+ }
228
+ if (hasDeno) {
229
+ const denoDefaultVersion = Option.isSome(args.denoDefault) ? args.denoDefault.value : void 0;
230
+ tasks.push(resolveDeno(args.deno.value, validatedIncrements, denoDefaultVersion, authLayerOverride));
231
+ }
232
+ const entries = yield* Effect.all(tasks, { concurrency: "unbounded" });
233
+ const results = Object.fromEntries(entries);
234
+ const hasError = Object.values(results).some((r) => !r.ok);
235
+ yield* Console.log(formatResponse(!hasError, results, args.pretty));
236
+ });
237
+
238
+ //#endregion
239
+ export { appIdOption, appInstallationIdOption, appPrivateKeyOption, bunDefaultOption, bunOption, denoDefaultOption, denoOption, incrementsOption, nodeDateOption, nodeDefaultOption, nodeOption, nodePhasesOption, prettyOption, resolveHandler, schemaOption, tokenOption };
@@ -0,0 +1,12 @@
1
+ import { CliResponse } from "./response.js";
2
+ import { JSONSchema } from "effect";
3
+
4
+ //#region src/cli/schemas/json-schema.ts
5
+ /**
6
+ * JSON Schema for the CLI response envelope.
7
+ * Used by --schema flag and can be published alongside the package.
8
+ */
9
+ const cliJsonSchema = JSONSchema.make(CliResponse);
10
+
11
+ //#endregion
12
+ export { cliJsonSchema };
@@ -0,0 +1,86 @@
1
+ import { Schema } from "effect";
2
+
3
+ //#region src/cli/schemas/response.ts
4
+ /**
5
+ * Structured error in CLI output. Uses _tag to identify the error type.
6
+ * Additional fields vary by error type and are passed through as-is.
7
+ */
8
+ const CliErrorDetail = Schema.Struct({
9
+ _tag: Schema.String.annotations({ description: "Error type identifier (e.g. RateLimitError, SemverError)" }),
10
+ message: Schema.String.annotations({ description: "Human-readable error message" })
11
+ }).pipe(Schema.extend(Schema.Record({
12
+ key: Schema.String,
13
+ value: Schema.Unknown.annotations({
14
+ description: "Additional error-specific metadata",
15
+ title: "ErrorMetadata"
16
+ })
17
+ })), Schema.annotations({
18
+ identifier: "CliErrorDetail",
19
+ description: "Structured error with type tag, message, and optional metadata"
20
+ }));
21
+ /**
22
+ * Successful resolution for a single runtime.
23
+ */
24
+ const CliRuntimeSuccess = Schema.Struct({
25
+ ok: Schema.Literal(true).annotations({ description: "Indicates successful resolution" }),
26
+ source: Schema.String.annotations({ description: "Data source: 'api' or 'cache'" }),
27
+ versions: Schema.Array(Schema.String.annotations({ description: "Semver version string" })).annotations({ description: "All resolved versions matching the semver range" }),
28
+ latest: Schema.String.annotations({ description: "Most recent version matching the range" }),
29
+ lts: Schema.optional(Schema.String.annotations({ description: "Current LTS version (Node.js only)" })),
30
+ default: Schema.optional(Schema.String.annotations({ description: "Default recommended version" }))
31
+ }).annotations({
32
+ identifier: "CliRuntimeSuccess",
33
+ description: "Successful version resolution for a single runtime"
34
+ });
35
+ /**
36
+ * Failed resolution for a single runtime.
37
+ */
38
+ const CliRuntimeError = Schema.Struct({
39
+ ok: Schema.Literal(false).annotations({ description: "Indicates failed resolution" }),
40
+ error: CliErrorDetail
41
+ }).annotations({
42
+ identifier: "CliRuntimeError",
43
+ description: "Failed version resolution for a single runtime"
44
+ });
45
+ /**
46
+ * Per-runtime result: success or error.
47
+ */
48
+ const CliRuntimeResult = Schema.Union(CliRuntimeSuccess, CliRuntimeError).annotations({
49
+ identifier: "CliRuntimeResult",
50
+ description: "Per-runtime resolution result: either success with versions or error with details"
51
+ });
52
+ const CliResultsMap = Schema.Record({
53
+ key: Schema.String.annotations({ description: "Runtime name (node, bun, or deno)" }),
54
+ value: CliRuntimeResult
55
+ }).annotations({ description: "Map of runtime name to resolution result" });
56
+ /**
57
+ * Full CLI success response — all runtimes resolved.
58
+ */
59
+ const CliSuccessResponse = Schema.Struct({
60
+ ok: Schema.Literal(true).annotations({ description: "True when all requested runtimes resolved successfully" }),
61
+ results: CliResultsMap
62
+ }).annotations({
63
+ identifier: "CliSuccessResponse",
64
+ description: "All requested runtimes resolved successfully"
65
+ });
66
+ /**
67
+ * Full CLI error response — at least one runtime failed.
68
+ */
69
+ const CliErrorResponse = Schema.Struct({
70
+ ok: Schema.Literal(false).annotations({ description: "False when one or more requested runtimes failed to resolve" }),
71
+ results: CliResultsMap
72
+ }).annotations({
73
+ identifier: "CliErrorResponse",
74
+ description: "One or more requested runtimes failed to resolve"
75
+ });
76
+ /**
77
+ * Top-level CLI response envelope.
78
+ */
79
+ const CliResponse = Schema.Union(CliSuccessResponse, CliErrorResponse).annotations({
80
+ identifier: "CliResponse",
81
+ title: "RuntimeResolverResponse",
82
+ description: "CLI response envelope. Contains an ok discriminator and a results map with per-runtime resolution outcomes."
83
+ });
84
+
85
+ //#endregion
86
+ export { CliResponse };