runtime-resolver 0.3.12 → 0.3.13
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.
- package/bin/runtime-resolver.d.ts +1 -0
- package/bin/runtime-resolver.js +30 -261
- package/bin/runtime-resolver.js.map +1 -0
- package/cli/commands/resolve.js +240 -0
- package/cli/commands/resolve.js.map +1 -0
- package/cli/schemas/json-schema.js +13 -0
- package/cli/schemas/json-schema.js.map +1 -0
- package/cli/schemas/response.js +87 -0
- package/cli/schemas/response.js.map +1 -0
- package/data/bun-defaults.js +851 -0
- package/data/bun-defaults.js.map +1 -0
- package/data/deno-defaults.js +1523 -0
- package/data/deno-defaults.js.map +1 -0
- package/data/node-defaults.js +4401 -0
- package/data/node-defaults.js.map +1 -0
- package/errors/AuthenticationError.js +36 -0
- package/errors/AuthenticationError.js.map +1 -0
- package/errors/CacheError.js +33 -0
- package/errors/CacheError.js.map +1 -0
- package/errors/FreshnessError.js +39 -0
- package/errors/FreshnessError.js.map +1 -0
- package/errors/InvalidInputError.js +34 -0
- package/errors/InvalidInputError.js.map +1 -0
- package/errors/NetworkError.js +35 -0
- package/errors/NetworkError.js.map +1 -0
- package/errors/ParseError.js +34 -0
- package/errors/ParseError.js.map +1 -0
- package/errors/RateLimitError.js +40 -0
- package/errors/RateLimitError.js.map +1 -0
- package/errors/VersionNotFoundError.js +38 -0
- package/errors/VersionNotFoundError.js.map +1 -0
- package/index.d.ts +2577 -2608
- package/index.d.ts.map +1 -0
- package/index.js +159 -132
- package/index.js.map +1 -0
- package/layers/AutoBunCacheLive.js +48 -0
- package/layers/AutoBunCacheLive.js.map +1 -0
- package/layers/AutoDenoCacheLive.js +48 -0
- package/layers/AutoDenoCacheLive.js.map +1 -0
- package/layers/AutoNodeCacheLive.js +59 -0
- package/layers/AutoNodeCacheLive.js.map +1 -0
- package/layers/BunReleaseCacheLive.js +26 -0
- package/layers/BunReleaseCacheLive.js.map +1 -0
- package/layers/BunResolverLive.js +86 -0
- package/layers/BunResolverLive.js.map +1 -0
- package/layers/BunVersionFetcherLive.js +56 -0
- package/layers/BunVersionFetcherLive.js.map +1 -0
- package/layers/DenoReleaseCacheLive.js +26 -0
- package/layers/DenoReleaseCacheLive.js.map +1 -0
- package/layers/DenoResolverLive.js +85 -0
- package/layers/DenoResolverLive.js.map +1 -0
- package/layers/DenoVersionFetcherLive.js +52 -0
- package/layers/DenoVersionFetcherLive.js.map +1 -0
- package/layers/FreshBunCacheLive.js +61 -0
- package/layers/FreshBunCacheLive.js.map +1 -0
- package/layers/FreshDenoCacheLive.js +61 -0
- package/layers/FreshDenoCacheLive.js.map +1 -0
- package/layers/FreshNodeCacheLive.js +56 -0
- package/layers/FreshNodeCacheLive.js.map +1 -0
- package/layers/GitHubAppAuth.js +59 -0
- package/layers/GitHubAppAuth.js.map +1 -0
- package/layers/GitHubAutoAuth.js +71 -0
- package/layers/GitHubAutoAuth.js.map +1 -0
- package/layers/GitHubClientLive.js +131 -0
- package/layers/GitHubClientLive.js.map +1 -0
- package/layers/GitHubTokenAuth.js +51 -0
- package/layers/GitHubTokenAuth.js.map +1 -0
- package/layers/NodeReleaseCacheLive.js +78 -0
- package/layers/NodeReleaseCacheLive.js.map +1 -0
- package/layers/NodeResolverLive.js +108 -0
- package/layers/NodeResolverLive.js.map +1 -0
- package/layers/NodeScheduleFetcherLive.js +38 -0
- package/layers/NodeScheduleFetcherLive.js.map +1 -0
- package/layers/NodeVersionFetcherLive.js +56 -0
- package/layers/NodeVersionFetcherLive.js.map +1 -0
- package/layers/OfflineBunCacheLive.js +41 -0
- package/layers/OfflineBunCacheLive.js.map +1 -0
- package/layers/OfflineDenoCacheLive.js +41 -0
- package/layers/OfflineDenoCacheLive.js.map +1 -0
- package/layers/OfflineNodeCacheLive.js +40 -0
- package/layers/OfflineNodeCacheLive.js.map +1 -0
- package/layers/RuntimeCacheLive.js +79 -0
- package/layers/RuntimeCacheLive.js.map +1 -0
- package/lib/retry.js +28 -0
- package/lib/retry.js.map +1 -0
- package/lib/semver.js +22 -0
- package/lib/semver.js.map +1 -0
- package/package.json +57 -68
- package/schemas/bun-release.js +62 -0
- package/schemas/bun-release.js.map +1 -0
- package/schemas/deno-release.js +62 -0
- package/schemas/deno-release.js.map +1 -0
- package/schemas/github.js +75 -0
- package/schemas/github.js.map +1 -0
- package/schemas/node-release.js +131 -0
- package/schemas/node-release.js.map +1 -0
- package/schemas/node-schedule.js +111 -0
- package/schemas/node-schedule.js.map +1 -0
- package/schemas/node.js +69 -0
- package/schemas/node.js.map +1 -0
- package/services/BunReleaseCache.js +34 -0
- package/services/BunReleaseCache.js.map +1 -0
- package/services/BunResolver.js +37 -0
- package/services/BunResolver.js.map +1 -0
- package/services/BunVersionFetcher.js +42 -0
- package/services/BunVersionFetcher.js.map +1 -0
- package/services/DenoReleaseCache.js +34 -0
- package/services/DenoReleaseCache.js.map +1 -0
- package/services/DenoResolver.js +37 -0
- package/services/DenoResolver.js.map +1 -0
- package/services/DenoVersionFetcher.js +42 -0
- package/services/DenoVersionFetcher.js.map +1 -0
- package/services/GitHubClient.js +41 -0
- package/services/GitHubClient.js.map +1 -0
- package/services/NodeReleaseCache.js +42 -0
- package/services/NodeReleaseCache.js.map +1 -0
- package/services/NodeResolver.js +45 -0
- package/services/NodeResolver.js.map +1 -0
- package/services/NodeScheduleFetcher.js +39 -0
- package/services/NodeScheduleFetcher.js.map +1 -0
- package/services/NodeVersionFetcher.js +40 -0
- package/services/NodeVersionFetcher.js.map +1 -0
- package/services/OctokitInstance.js +45 -0
- package/services/OctokitInstance.js.map +1 -0
- package/tsdoc-metadata.json +11 -11
- package/612.js +0 -23
- package/711.js +0 -7554
- package/json-schema.js +0 -95
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitHubAppAuth.js","names":[],"sources":["../../../../src/layers/GitHubAppAuth.ts"],"sourcesContent":["import { createAppAuth } from \"@octokit/auth-app\";\nimport { Effect, Layer } from \"effect\";\nimport { Octokit } from \"octokit\";\nimport { AuthenticationError } from \"../errors/AuthenticationError.js\";\nimport { OctokitInstance } from \"../services/OctokitInstance.js\";\n\n/**\n * Configuration required to authenticate as a GitHub App installation.\n *\n * @see {@link GitHubAppAuth}\n * @public\n */\nexport interface GitHubAppAuthConfig {\n\treadonly appId: string;\n\treadonly privateKey: string;\n\treadonly installationId?: number;\n}\n\nconst resolveInstallationId = async (\n\tauth: (opts: { type: \"app\" }) => Promise<{ token: string }>,\n\tinstallationId?: number,\n): Promise<number> => {\n\tif (installationId) return installationId;\n\n\tconst { token: jwt } = await auth({ type: \"app\" });\n\n\tconst response = await fetch(\"https://api.github.com/app/installations?per_page=100\", {\n\t\theaders: {\n\t\t\tAuthorization: `Bearer ${jwt}`,\n\t\t\tAccept: \"application/vnd.github+json\",\n\t\t},\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to list installations: ${response.status}`);\n\t}\n\n\tconst installations = (await response.json()) as Array<{ id: number }>;\n\n\tif (installations.length === 0) {\n\t\tthrow new Error(\"No installations found for this GitHub App\");\n\t}\n\n\t// Use the first installation. Most apps have a single installation;\n\t// callers with multiple should pass installationId explicitly.\n\treturn installations[0].id;\n};\n\n/**\n * Provides an {@link OctokitInstance} authenticated as a GitHub App installation.\n *\n * Accepts a {@link GitHubAppAuthConfig} and uses `@octokit/auth-app` to obtain\n * an installation access token. If `installationId` is omitted, the first\n * installation returned by the GitHub API is used automatically (suitable for\n * apps with a single installation). If authentication fails for any reason the\n * layer fails with an `AuthenticationError`.\n *\n * Use this auth method when your integration runs as a GitHub App rather than\n * as an individual user or a CI token.\n *\n * @param config - App credentials and optional installation ID.\n *\n * @see {@link GitHubAppAuthConfig}\n * @see {@link OctokitInstance}\n * @see {@link GitHubAutoAuth}\n * @see {@link GitHubTokenAuth}\n * @public\n */\nexport const GitHubAppAuth = (config: GitHubAppAuthConfig): Layer.Layer<OctokitInstance, AuthenticationError> =>\n\tLayer.effect(\n\t\tOctokitInstance,\n\t\tEffect.tryPromise({\n\t\t\ttry: async () => {\n\t\t\t\tconst auth = createAppAuth({\n\t\t\t\t\tappId: config.appId,\n\t\t\t\t\tprivateKey: config.privateKey,\n\t\t\t\t});\n\n\t\t\t\tconst resolvedId = await resolveInstallationId(auth, config.installationId);\n\n\t\t\t\tconst result = await auth({\n\t\t\t\t\ttype: \"installation\",\n\t\t\t\t\tinstallationId: resolvedId,\n\t\t\t\t});\n\n\t\t\t\treturn new Octokit({ auth: result.token });\n\t\t\t},\n\t\t\tcatch: (error) =>\n\t\t\t\tnew AuthenticationError({\n\t\t\t\t\tmethod: \"app\",\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t}),\n\t\t}),\n\t);\n"],"mappings":";;;;;;;AAkBA,MAAM,wBAAwB,OAC7B,MACA,mBACqB;CACrB,IAAI,gBAAgB,OAAO;CAE3B,MAAM,EAAE,OAAO,QAAQ,MAAM,KAAK,EAAE,MAAM,MAAM,CAAC;CAEjD,MAAM,WAAW,MAAM,MAAM,yDAAyD,EACrF,SAAS;EACR,eAAe,UAAU;EACzB,QAAQ;CACT,EACD,CAAC;CAED,IAAI,CAAC,SAAS,IACb,MAAM,IAAI,MAAM,iCAAiC,SAAS,QAAQ;CAGnE,MAAM,gBAAiB,MAAM,SAAS,KAAK;CAE3C,IAAI,cAAc,WAAW,GAC5B,MAAM,IAAI,MAAM,4CAA4C;CAK7D,OAAO,cAAc,EAAE,CAAC;AACzB;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,iBAAiB,WAC7B,MAAM,OACL,iBACA,OAAO,WAAW;CACjB,KAAK,YAAY;EAChB,MAAM,OAAO,cAAc;GAC1B,OAAO,OAAO;GACd,YAAY,OAAO;EACpB,CAAC;EASD,OAAO,IAAI,QAAQ,EAAE,OAAM,MALN,KAAK;GACzB,MAAM;GACN,gBAAgB,MAJQ,sBAAsB,MAAM,OAAO,cAAc;EAK1E,CAAC,EAEgC,CAAC,MAAM,CAAC;CAC1C;CACA,QAAQ,UACP,IAAI,oBAAoB;EACvB,QAAQ;EACR,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;CAC/D,CAAC;AACH,CAAC,CACF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { OctokitInstance } from "../services/OctokitInstance.js";
|
|
2
|
+
import { GitHubAppAuth } from "./GitHubAppAuth.js";
|
|
3
|
+
import { Effect, Layer } from "effect";
|
|
4
|
+
import { Octokit } from "octokit";
|
|
5
|
+
|
|
6
|
+
//#region src/layers/GitHubAutoAuth.ts
|
|
7
|
+
/**
|
|
8
|
+
* Provides an {@link OctokitInstance} by auto-detecting the available
|
|
9
|
+
* authentication credentials from environment variables at layer construction
|
|
10
|
+
* time.
|
|
11
|
+
*
|
|
12
|
+
* Credentials are evaluated in the following priority order:
|
|
13
|
+
* 1. **GitHub App** — when both `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`
|
|
14
|
+
* are set, authenticates as a GitHub App installation via {@link GitHubAppAuth}.
|
|
15
|
+
* `GITHUB_APP_INSTALLATION_ID` is optional; if absent the first installation
|
|
16
|
+
* is used automatically.
|
|
17
|
+
* 2. **Personal Access Token** — when `GITHUB_PERSONAL_ACCESS_TOKEN` is set,
|
|
18
|
+
* authenticates with that token.
|
|
19
|
+
* 3. **`GITHUB_TOKEN`** — when `GITHUB_TOKEN` is set (e.g., the default token
|
|
20
|
+
* injected in GitHub Actions), authenticates with that token.
|
|
21
|
+
* 4. **Unauthenticated** — falls back to an unauthenticated Octokit instance
|
|
22
|
+
* when no credentials are found (lower rate limits apply).
|
|
23
|
+
*
|
|
24
|
+
* If both App and token credentials are present, App auth takes precedence and
|
|
25
|
+
* a warning is logged. This layer fails with `AuthenticationError` only when
|
|
26
|
+
* App credentials are present but the App authentication itself fails.
|
|
27
|
+
*
|
|
28
|
+
* Use this layer as the default auth strategy in most applications. Switch to
|
|
29
|
+
* {@link GitHubTokenAuth} or {@link GitHubAppAuth} when you need explicit control
|
|
30
|
+
* over which credential source is used.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { GitHubClientLive, GitHubAutoAuth } from "runtime-resolver";
|
|
35
|
+
* import { Layer } from "effect";
|
|
36
|
+
*
|
|
37
|
+
* const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* @see {@link OctokitInstance}
|
|
41
|
+
* @see {@link GitHubTokenAuth}
|
|
42
|
+
* @see {@link GitHubAppAuth}
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
const GitHubAutoAuth = Layer.effect(OctokitInstance, Effect.gen(function* () {
|
|
46
|
+
const appId = process.env.GITHUB_APP_ID;
|
|
47
|
+
const privateKey = process.env.GITHUB_APP_PRIVATE_KEY;
|
|
48
|
+
const installationId = process.env.GITHUB_APP_INSTALLATION_ID;
|
|
49
|
+
const pat = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
|
|
50
|
+
const token = process.env.GITHUB_TOKEN;
|
|
51
|
+
const hasApp = appId && privateKey;
|
|
52
|
+
if (hasApp && (pat || token)) {
|
|
53
|
+
const tokenSource = pat ? "GITHUB_PERSONAL_ACCESS_TOKEN" : "GITHUB_TOKEN";
|
|
54
|
+
yield* Effect.logWarning(`Multiple GitHub credential sources found. Using GitHub App authentication (from GITHUB_APP_ID). Ignoring ${tokenSource}.`);
|
|
55
|
+
}
|
|
56
|
+
if (hasApp) {
|
|
57
|
+
const appLayer = GitHubAppAuth({
|
|
58
|
+
appId,
|
|
59
|
+
privateKey,
|
|
60
|
+
...installationId ? { installationId: Number(installationId) } : {}
|
|
61
|
+
});
|
|
62
|
+
return yield* Effect.provide(OctokitInstance, appLayer);
|
|
63
|
+
}
|
|
64
|
+
if (pat) return new Octokit({ auth: pat });
|
|
65
|
+
if (token) return new Octokit({ auth: token });
|
|
66
|
+
return new Octokit();
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { GitHubAutoAuth };
|
|
71
|
+
//# sourceMappingURL=GitHubAutoAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitHubAutoAuth.js","names":[],"sources":["../../../../src/layers/GitHubAutoAuth.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\nimport { Octokit } from \"octokit\";\nimport type { AuthenticationError } from \"../errors/AuthenticationError.js\";\nimport { OctokitInstance } from \"../services/OctokitInstance.js\";\nimport { GitHubAppAuth } from \"./GitHubAppAuth.js\";\n\n/**\n * Provides an {@link OctokitInstance} by auto-detecting the available\n * authentication credentials from environment variables at layer construction\n * time.\n *\n * Credentials are evaluated in the following priority order:\n * 1. **GitHub App** — when both `GITHUB_APP_ID` and `GITHUB_APP_PRIVATE_KEY`\n * are set, authenticates as a GitHub App installation via {@link GitHubAppAuth}.\n * `GITHUB_APP_INSTALLATION_ID` is optional; if absent the first installation\n * is used automatically.\n * 2. **Personal Access Token** — when `GITHUB_PERSONAL_ACCESS_TOKEN` is set,\n * authenticates with that token.\n * 3. **`GITHUB_TOKEN`** — when `GITHUB_TOKEN` is set (e.g., the default token\n * injected in GitHub Actions), authenticates with that token.\n * 4. **Unauthenticated** — falls back to an unauthenticated Octokit instance\n * when no credentials are found (lower rate limits apply).\n *\n * If both App and token credentials are present, App auth takes precedence and\n * a warning is logged. This layer fails with `AuthenticationError` only when\n * App credentials are present but the App authentication itself fails.\n *\n * Use this layer as the default auth strategy in most applications. Switch to\n * {@link GitHubTokenAuth} or {@link GitHubAppAuth} when you need explicit control\n * over which credential source is used.\n *\n * @example\n * ```ts\n * import { GitHubClientLive, GitHubAutoAuth } from \"runtime-resolver\";\n * import { Layer } from \"effect\";\n *\n * const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));\n * ```\n *\n * @see {@link OctokitInstance}\n * @see {@link GitHubTokenAuth}\n * @see {@link GitHubAppAuth}\n * @public\n */\nexport const GitHubAutoAuth: Layer.Layer<OctokitInstance, AuthenticationError> = Layer.effect(\n\tOctokitInstance,\n\tEffect.gen(function* () {\n\t\tconst appId = process.env.GITHUB_APP_ID;\n\t\tconst privateKey = process.env.GITHUB_APP_PRIVATE_KEY;\n\t\tconst installationId = process.env.GITHUB_APP_INSTALLATION_ID;\n\t\tconst pat = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;\n\t\tconst token = process.env.GITHUB_TOKEN;\n\n\t\tconst hasApp = appId && privateKey;\n\t\tconst hasToken = pat || token;\n\n\t\t// Warn when multiple credential sources are detected\n\t\tif (hasApp && hasToken) {\n\t\t\tconst tokenSource = pat ? \"GITHUB_PERSONAL_ACCESS_TOKEN\" : \"GITHUB_TOKEN\";\n\t\t\tyield* Effect.logWarning(\n\t\t\t\t`Multiple GitHub credential sources found. Using GitHub App authentication (from GITHUB_APP_ID). Ignoring ${tokenSource}.`,\n\t\t\t);\n\t\t}\n\n\t\t// Priority 1: App env vars\n\t\tif (hasApp) {\n\t\t\tconst appLayer = GitHubAppAuth({\n\t\t\t\tappId: appId,\n\t\t\t\tprivateKey: privateKey,\n\t\t\t\t...(installationId ? { installationId: Number(installationId) } : {}),\n\t\t\t});\n\t\t\treturn yield* Effect.provide(OctokitInstance, appLayer);\n\t\t}\n\n\t\t// Priority 2: Token env vars (PAT first, then GITHUB_TOKEN)\n\t\tif (pat) {\n\t\t\treturn new Octokit({ auth: pat });\n\t\t}\n\t\tif (token) {\n\t\t\treturn new Octokit({ auth: token });\n\t\t}\n\n\t\t// Priority 3: Unauthenticated\n\t\treturn new Octokit();\n\t}),\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,MAAa,iBAAoE,MAAM,OACtF,iBACA,OAAO,IAAI,aAAa;CACvB,MAAM,QAAQ,QAAQ,IAAI;CAC1B,MAAM,aAAa,QAAQ,IAAI;CAC/B,MAAM,iBAAiB,QAAQ,IAAI;CACnC,MAAM,MAAM,QAAQ,IAAI;CACxB,MAAM,QAAQ,QAAQ,IAAI;CAE1B,MAAM,SAAS,SAAS;CAIxB,IAAI,WAHa,OAAO,QAGA;EACvB,MAAM,cAAc,MAAM,iCAAiC;EAC3D,OAAO,OAAO,WACb,4GAA4G,YAAY,EACzH;CACD;CAGA,IAAI,QAAQ;EACX,MAAM,WAAW,cAAc;GACvB;GACK;GACZ,GAAI,iBAAiB,EAAE,gBAAgB,OAAO,cAAc,EAAE,IAAI,CAAC;EACpE,CAAC;EACD,OAAO,OAAO,OAAO,QAAQ,iBAAiB,QAAQ;CACvD;CAGA,IAAI,KACH,OAAO,IAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;CAEjC,IAAI,OACH,OAAO,IAAI,QAAQ,EAAE,MAAM,MAAM,CAAC;CAInC,OAAO,IAAI,QAAQ;AACpB,CAAC,CACF"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { GitHubClient } from "../services/GitHubClient.js";
|
|
2
|
+
import { OctokitInstance } from "../services/OctokitInstance.js";
|
|
3
|
+
import { AuthenticationError } from "../errors/AuthenticationError.js";
|
|
4
|
+
import { NetworkError } from "../errors/NetworkError.js";
|
|
5
|
+
import { ParseError } from "../errors/ParseError.js";
|
|
6
|
+
import { RateLimitError } from "../errors/RateLimitError.js";
|
|
7
|
+
import { GitHubReleaseList, GitHubTagList } from "../schemas/github.js";
|
|
8
|
+
import { Effect, Layer, Schema } from "effect";
|
|
9
|
+
|
|
10
|
+
//#region src/layers/GitHubClientLive.ts
|
|
11
|
+
const mapOctokitError = (error, url) => {
|
|
12
|
+
const err = error;
|
|
13
|
+
if (err.status === 401) return new AuthenticationError({
|
|
14
|
+
method: "token",
|
|
15
|
+
message: `GitHub API authentication failed for ${url}`
|
|
16
|
+
});
|
|
17
|
+
if (err.status === 403 || err.status === 429) {
|
|
18
|
+
const headers = err.response?.headers ?? {};
|
|
19
|
+
const retryAfterStr = headers["retry-after"];
|
|
20
|
+
return new RateLimitError({
|
|
21
|
+
...retryAfterStr ? { retryAfter: Number.parseInt(retryAfterStr, 10) } : {},
|
|
22
|
+
limit: headers["x-ratelimit-limit"] ? Number.parseInt(headers["x-ratelimit-limit"], 10) : 0,
|
|
23
|
+
remaining: headers["x-ratelimit-remaining"] ? Number.parseInt(headers["x-ratelimit-remaining"], 10) : 0,
|
|
24
|
+
message: `GitHub API rate limit exceeded for ${url}`
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return new NetworkError({
|
|
28
|
+
url,
|
|
29
|
+
...err.status !== void 0 ? { status: err.status } : {},
|
|
30
|
+
message: error instanceof Error ? error.message : String(error)
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const decodeTagList = Schema.decodeUnknown(GitHubTagList);
|
|
34
|
+
const decodeReleaseList = Schema.decodeUnknown(GitHubReleaseList);
|
|
35
|
+
/**
|
|
36
|
+
* Provides the {@link GitHubClient} service backed by an {@link OctokitInstance}.
|
|
37
|
+
*
|
|
38
|
+
* This layer wraps an Octokit instance and exposes a typed, Effect-native API
|
|
39
|
+
* for listing GitHub releases, listing tags, and fetching arbitrary JSON from
|
|
40
|
+
* URLs. HTTP errors are mapped to typed domain errors (`NetworkError`,
|
|
41
|
+
* `RateLimitError`, `AuthenticationError`) so they can be handled precisely in
|
|
42
|
+
* calling code.
|
|
43
|
+
*
|
|
44
|
+
* Pair this layer with one of the auth layers to form a complete GitHub
|
|
45
|
+
* integration stack:
|
|
46
|
+
* - {@link GitHubAutoAuth} — auto-detects auth from environment variables
|
|
47
|
+
* - {@link GitHubTokenAuth} — authenticates using a personal access token
|
|
48
|
+
* - {@link GitHubAppAuth} — authenticates as a GitHub App installation
|
|
49
|
+
*
|
|
50
|
+
* @see {@link GitHubClient}
|
|
51
|
+
* @see {@link OctokitInstance}
|
|
52
|
+
* @see {@link GitHubAutoAuth}
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
const GitHubClientLive = Layer.effect(GitHubClient, Effect.gen(function* () {
|
|
56
|
+
const octokit = yield* OctokitInstance;
|
|
57
|
+
return {
|
|
58
|
+
listTags: (owner, repo, options) => Effect.gen(function* () {
|
|
59
|
+
const perPage = options?.perPage ?? 100;
|
|
60
|
+
const maxPages = options?.pages ?? Number.POSITIVE_INFINITY;
|
|
61
|
+
const allTags = [];
|
|
62
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
63
|
+
const response = yield* Effect.tryPromise({
|
|
64
|
+
try: () => octokit.rest.repos.listTags({
|
|
65
|
+
owner,
|
|
66
|
+
repo,
|
|
67
|
+
per_page: perPage,
|
|
68
|
+
page
|
|
69
|
+
}),
|
|
70
|
+
catch: (error) => mapOctokitError(error, `repos/${owner}/${repo}/tags`)
|
|
71
|
+
});
|
|
72
|
+
const tags = yield* decodeTagList(response.data).pipe(Effect.mapError((e) => new ParseError({
|
|
73
|
+
source: `repos/${owner}/${repo}/tags`,
|
|
74
|
+
message: `Schema validation failed: ${e.message}`
|
|
75
|
+
})));
|
|
76
|
+
allTags.push(...tags);
|
|
77
|
+
if (response.data.length < perPage) break;
|
|
78
|
+
}
|
|
79
|
+
return allTags;
|
|
80
|
+
}),
|
|
81
|
+
listReleases: (owner, repo, options) => Effect.gen(function* () {
|
|
82
|
+
const perPage = options?.perPage ?? 100;
|
|
83
|
+
const maxPages = options?.pages ?? Number.POSITIVE_INFINITY;
|
|
84
|
+
const allReleases = [];
|
|
85
|
+
for (let page = 1; page <= maxPages; page++) {
|
|
86
|
+
const response = yield* Effect.tryPromise({
|
|
87
|
+
try: () => octokit.rest.repos.listReleases({
|
|
88
|
+
owner,
|
|
89
|
+
repo,
|
|
90
|
+
per_page: perPage,
|
|
91
|
+
page
|
|
92
|
+
}),
|
|
93
|
+
catch: (error) => mapOctokitError(error, `repos/${owner}/${repo}/releases`)
|
|
94
|
+
});
|
|
95
|
+
const releases = yield* decodeReleaseList(response.data).pipe(Effect.mapError((e) => new ParseError({
|
|
96
|
+
source: `repos/${owner}/${repo}/releases`,
|
|
97
|
+
message: `Schema validation failed: ${e.message}`
|
|
98
|
+
})));
|
|
99
|
+
allReleases.push(...releases);
|
|
100
|
+
if (response.data.length < perPage) break;
|
|
101
|
+
}
|
|
102
|
+
return allReleases;
|
|
103
|
+
}),
|
|
104
|
+
getJson: (url, schema) => Effect.gen(function* () {
|
|
105
|
+
const response = yield* Effect.tryPromise({
|
|
106
|
+
try: () => fetch(url),
|
|
107
|
+
catch: (error) => new NetworkError({
|
|
108
|
+
url,
|
|
109
|
+
message: error instanceof Error ? error.message : String(error)
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) yield* Effect.fail(new NetworkError({
|
|
113
|
+
url,
|
|
114
|
+
status: response.status,
|
|
115
|
+
message: `HTTP ${response.status}: ${response.statusText}`
|
|
116
|
+
}));
|
|
117
|
+
const json = yield* Effect.tryPromise({
|
|
118
|
+
try: () => response.json(),
|
|
119
|
+
catch: () => new ParseError({
|
|
120
|
+
source: url,
|
|
121
|
+
message: "Failed to parse JSON response"
|
|
122
|
+
})
|
|
123
|
+
});
|
|
124
|
+
return yield* schema.decode(json);
|
|
125
|
+
})
|
|
126
|
+
};
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
export { GitHubClientLive };
|
|
131
|
+
//# sourceMappingURL=GitHubClientLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitHubClientLive.js","names":[],"sources":["../../../../src/layers/GitHubClientLive.ts"],"sourcesContent":["import { Effect, Layer, Schema } from \"effect\";\nimport { AuthenticationError } from \"../errors/AuthenticationError.js\";\nimport { NetworkError } from \"../errors/NetworkError.js\";\nimport { ParseError } from \"../errors/ParseError.js\";\nimport { RateLimitError } from \"../errors/RateLimitError.js\";\nimport { GitHubReleaseList, GitHubTagList } from \"../schemas/github.js\";\nimport type { ListOptions } from \"../services/GitHubClient.js\";\nimport { GitHubClient } from \"../services/GitHubClient.js\";\nimport { OctokitInstance } from \"../services/OctokitInstance.js\";\n\nconst mapOctokitError = (error: unknown, url: string): NetworkError | RateLimitError | AuthenticationError => {\n\tconst err = error as { status?: number; response?: { headers?: Record<string, string> } };\n\n\tif (err.status === 401) {\n\t\treturn new AuthenticationError({\n\t\t\tmethod: \"token\",\n\t\t\tmessage: `GitHub API authentication failed for ${url}`,\n\t\t});\n\t}\n\n\tif (err.status === 403 || err.status === 429) {\n\t\tconst headers = err.response?.headers ?? {};\n\t\tconst retryAfterStr = headers[\"retry-after\"];\n\t\treturn new RateLimitError({\n\t\t\t...(retryAfterStr ? { retryAfter: Number.parseInt(retryAfterStr, 10) } : {}),\n\t\t\tlimit: headers[\"x-ratelimit-limit\"] ? Number.parseInt(headers[\"x-ratelimit-limit\"], 10) : 0,\n\t\t\tremaining: headers[\"x-ratelimit-remaining\"] ? Number.parseInt(headers[\"x-ratelimit-remaining\"], 10) : 0,\n\t\t\tmessage: `GitHub API rate limit exceeded for ${url}`,\n\t\t});\n\t}\n\n\treturn new NetworkError({\n\t\turl,\n\t\t...(err.status !== undefined ? { status: err.status } : {}),\n\t\tmessage: error instanceof Error ? error.message : String(error),\n\t});\n};\n\nconst decodeTagList = Schema.decodeUnknown(GitHubTagList);\nconst decodeReleaseList = Schema.decodeUnknown(GitHubReleaseList);\n\n/**\n * Provides the {@link GitHubClient} service backed by an {@link OctokitInstance}.\n *\n * This layer wraps an Octokit instance and exposes a typed, Effect-native API\n * for listing GitHub releases, listing tags, and fetching arbitrary JSON from\n * URLs. HTTP errors are mapped to typed domain errors (`NetworkError`,\n * `RateLimitError`, `AuthenticationError`) so they can be handled precisely in\n * calling code.\n *\n * Pair this layer with one of the auth layers to form a complete GitHub\n * integration stack:\n * - {@link GitHubAutoAuth} — auto-detects auth from environment variables\n * - {@link GitHubTokenAuth} — authenticates using a personal access token\n * - {@link GitHubAppAuth} — authenticates as a GitHub App installation\n *\n * @see {@link GitHubClient}\n * @see {@link OctokitInstance}\n * @see {@link GitHubAutoAuth}\n * @public\n */\nexport const GitHubClientLive: Layer.Layer<GitHubClient, never, OctokitInstance> = Layer.effect(\n\tGitHubClient,\n\tEffect.gen(function* () {\n\t\tconst octokit = yield* OctokitInstance;\n\n\t\treturn {\n\t\t\tlistTags: (owner, repo, options?: ListOptions) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst perPage = options?.perPage ?? 100;\n\t\t\t\t\tconst maxPages = options?.pages ?? Number.POSITIVE_INFINITY;\n\t\t\t\t\tconst allTags: Array<(typeof GitHubTagList.Type)[number]> = [];\n\n\t\t\t\t\tfor (let page = 1; page <= maxPages; page++) {\n\t\t\t\t\t\tconst response = yield* Effect.tryPromise({\n\t\t\t\t\t\t\ttry: () =>\n\t\t\t\t\t\t\t\toctokit.rest.repos.listTags({\n\t\t\t\t\t\t\t\t\towner,\n\t\t\t\t\t\t\t\t\trepo,\n\t\t\t\t\t\t\t\t\tper_page: perPage,\n\t\t\t\t\t\t\t\t\tpage,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tcatch: (error) => mapOctokitError(error, `repos/${owner}/${repo}/tags`),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst tags = yield* decodeTagList(response.data).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(e) =>\n\t\t\t\t\t\t\t\t\tnew ParseError({\n\t\t\t\t\t\t\t\t\t\tsource: `repos/${owner}/${repo}/tags`,\n\t\t\t\t\t\t\t\t\t\tmessage: `Schema validation failed: ${e.message}`,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tallTags.push(...tags);\n\n\t\t\t\t\t\tif (response.data.length < perPage) break;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn allTags;\n\t\t\t\t}),\n\n\t\t\tlistReleases: (owner, repo, options?: ListOptions) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst perPage = options?.perPage ?? 100;\n\t\t\t\t\tconst maxPages = options?.pages ?? Number.POSITIVE_INFINITY;\n\t\t\t\t\tconst allReleases: Array<(typeof GitHubReleaseList.Type)[number]> = [];\n\n\t\t\t\t\tfor (let page = 1; page <= maxPages; page++) {\n\t\t\t\t\t\tconst response = yield* Effect.tryPromise({\n\t\t\t\t\t\t\ttry: () =>\n\t\t\t\t\t\t\t\toctokit.rest.repos.listReleases({\n\t\t\t\t\t\t\t\t\towner,\n\t\t\t\t\t\t\t\t\trepo,\n\t\t\t\t\t\t\t\t\tper_page: perPage,\n\t\t\t\t\t\t\t\t\tpage,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tcatch: (error) => mapOctokitError(error, `repos/${owner}/${repo}/releases`),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\tconst releases = yield* decodeReleaseList(response.data).pipe(\n\t\t\t\t\t\t\tEffect.mapError(\n\t\t\t\t\t\t\t\t(e) =>\n\t\t\t\t\t\t\t\t\tnew ParseError({\n\t\t\t\t\t\t\t\t\t\tsource: `repos/${owner}/${repo}/releases`,\n\t\t\t\t\t\t\t\t\t\tmessage: `Schema validation failed: ${e.message}`,\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\n\t\t\t\t\t\tallReleases.push(...releases);\n\n\t\t\t\t\t\tif (response.data.length < perPage) break;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn allReleases;\n\t\t\t\t}),\n\n\t\t\tgetJson: <A>(url: string, schema: { readonly decode: (input: unknown) => Effect.Effect<A, ParseError> }) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst response = yield* Effect.tryPromise({\n\t\t\t\t\t\ttry: () => fetch(url),\n\t\t\t\t\t\tcatch: (error) =>\n\t\t\t\t\t\t\tnew NetworkError({\n\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\tyield* Effect.fail(\n\t\t\t\t\t\t\tnew NetworkError({\n\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\t\t\tmessage: `HTTP ${response.status}: ${response.statusText}`,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst json = yield* Effect.tryPromise({\n\t\t\t\t\t\ttry: () => response.json(),\n\t\t\t\t\t\tcatch: () =>\n\t\t\t\t\t\t\tnew ParseError({\n\t\t\t\t\t\t\t\tsource: url,\n\t\t\t\t\t\t\t\tmessage: \"Failed to parse JSON response\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t});\n\n\t\t\t\t\treturn yield* schema.decode(json);\n\t\t\t\t}),\n\t\t};\n\t}),\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { OctokitInstance } from "../services/OctokitInstance.js";
|
|
2
|
+
import { Effect, Layer } from "effect";
|
|
3
|
+
import { Octokit } from "octokit";
|
|
4
|
+
|
|
5
|
+
//#region src/layers/GitHubTokenAuth.ts
|
|
6
|
+
const resolveToken = Effect.sync(() => process.env.GITHUB_PERSONAL_ACCESS_TOKEN || process.env.GITHUB_TOKEN || "");
|
|
7
|
+
/**
|
|
8
|
+
* Provides an {@link OctokitInstance} authenticated with a GitHub personal
|
|
9
|
+
* access token read from the environment at layer construction time.
|
|
10
|
+
*
|
|
11
|
+
* The token is resolved by checking, in order:
|
|
12
|
+
* 1. `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable
|
|
13
|
+
* 2. `GITHUB_TOKEN` environment variable
|
|
14
|
+
*
|
|
15
|
+
* If neither variable is set the layer constructs an unauthenticated Octokit
|
|
16
|
+
* instance (lower rate limits apply). This layer never fails.
|
|
17
|
+
*
|
|
18
|
+
* Use {@link GitHubTokenAuthFromToken} when you have the token value available
|
|
19
|
+
* programmatically at construction time.
|
|
20
|
+
*
|
|
21
|
+
* @see {@link OctokitInstance}
|
|
22
|
+
* @see {@link GitHubTokenAuthFromToken}
|
|
23
|
+
* @see {@link GitHubAutoAuth}
|
|
24
|
+
* @see {@link GitHubAppAuth}
|
|
25
|
+
* @public
|
|
26
|
+
*/
|
|
27
|
+
const GitHubTokenAuth = Layer.effect(OctokitInstance, Effect.gen(function* () {
|
|
28
|
+
const token = yield* resolveToken;
|
|
29
|
+
return new Octokit(token ? { auth: token } : {});
|
|
30
|
+
}));
|
|
31
|
+
/**
|
|
32
|
+
* Provides an {@link OctokitInstance} authenticated with the given personal
|
|
33
|
+
* access token string.
|
|
34
|
+
*
|
|
35
|
+
* Use this variant when the token is available as a value in your program
|
|
36
|
+
* rather than read from an environment variable. The resulting layer never
|
|
37
|
+
* fails.
|
|
38
|
+
*
|
|
39
|
+
* @param token - A GitHub personal access token string.
|
|
40
|
+
*
|
|
41
|
+
* @see {@link OctokitInstance}
|
|
42
|
+
* @see {@link GitHubTokenAuth}
|
|
43
|
+
* @see {@link GitHubAutoAuth}
|
|
44
|
+
* @see {@link GitHubAppAuth}
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
const GitHubTokenAuthFromToken = (token) => Layer.succeed(OctokitInstance, new Octokit({ auth: token }));
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { GitHubTokenAuth, GitHubTokenAuthFromToken };
|
|
51
|
+
//# sourceMappingURL=GitHubTokenAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitHubTokenAuth.js","names":[],"sources":["../../../../src/layers/GitHubTokenAuth.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\nimport { Octokit } from \"octokit\";\nimport { OctokitInstance } from \"../services/OctokitInstance.js\";\n\nconst resolveToken = Effect.sync(\n\t(): string => process.env.GITHUB_PERSONAL_ACCESS_TOKEN || process.env.GITHUB_TOKEN || \"\",\n);\n\n/**\n * Provides an {@link OctokitInstance} authenticated with a GitHub personal\n * access token read from the environment at layer construction time.\n *\n * The token is resolved by checking, in order:\n * 1. `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable\n * 2. `GITHUB_TOKEN` environment variable\n *\n * If neither variable is set the layer constructs an unauthenticated Octokit\n * instance (lower rate limits apply). This layer never fails.\n *\n * Use {@link GitHubTokenAuthFromToken} when you have the token value available\n * programmatically at construction time.\n *\n * @see {@link OctokitInstance}\n * @see {@link GitHubTokenAuthFromToken}\n * @see {@link GitHubAutoAuth}\n * @see {@link GitHubAppAuth}\n * @public\n */\nexport const GitHubTokenAuth: Layer.Layer<OctokitInstance> = Layer.effect(\n\tOctokitInstance,\n\tEffect.gen(function* () {\n\t\tconst token = yield* resolveToken;\n\t\treturn new Octokit(token ? { auth: token } : {});\n\t}),\n);\n\n/**\n * Provides an {@link OctokitInstance} authenticated with the given personal\n * access token string.\n *\n * Use this variant when the token is available as a value in your program\n * rather than read from an environment variable. The resulting layer never\n * fails.\n *\n * @param token - A GitHub personal access token string.\n *\n * @see {@link OctokitInstance}\n * @see {@link GitHubTokenAuth}\n * @see {@link GitHubAutoAuth}\n * @see {@link GitHubAppAuth}\n * @public\n */\nexport const GitHubTokenAuthFromToken = (token: string): Layer.Layer<OctokitInstance> =>\n\tLayer.succeed(OctokitInstance, new Octokit({ auth: token }));\n"],"mappings":";;;;;AAIA,MAAM,eAAe,OAAO,WACb,QAAQ,IAAI,gCAAgC,QAAQ,IAAI,gBAAgB,EACvF;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,kBAAgD,MAAM,OAClE,iBACA,OAAO,IAAI,aAAa;CACvB,MAAM,QAAQ,OAAO;CACrB,OAAO,IAAI,QAAQ,QAAQ,EAAE,MAAM,MAAM,IAAI,CAAC,CAAC;AAChD,CAAC,CACF;;;;;;;;;;;;;;;;;AAkBA,MAAa,4BAA4B,UACxC,MAAM,QAAQ,iBAAiB,IAAI,QAAQ,EAAE,MAAM,MAAM,CAAC,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { createRuntimeCache } from "./RuntimeCacheLive.js";
|
|
2
|
+
import { NodeReleaseCache } from "../services/NodeReleaseCache.js";
|
|
3
|
+
import { NodeRelease } from "../schemas/node-release.js";
|
|
4
|
+
import { NodeSchedule } from "../schemas/node-schedule.js";
|
|
5
|
+
import { Effect, Layer, Ref } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/layers/NodeReleaseCacheLive.ts
|
|
8
|
+
/**
|
|
9
|
+
* Low-level layer that constructs the {@link NodeReleaseCache} service backed by
|
|
10
|
+
* a `SemVerVersionCache` instance from `semver-effect`.
|
|
11
|
+
*
|
|
12
|
+
* This layer is more complex than the Bun and Deno equivalents because it manages
|
|
13
|
+
* additional LTS schedule state via an internal `Ref`. The schedule is stored
|
|
14
|
+
* separately from the version list so that calling `updateSchedule` can
|
|
15
|
+
* transparently rebuild all release objects with the latest phase information
|
|
16
|
+
* without reloading data from the network.
|
|
17
|
+
*
|
|
18
|
+
* This layer wires up the cache storage but does not populate it with any
|
|
19
|
+
* release data. Callers are responsible for calling `updateSchedule` and
|
|
20
|
+
* `loadFromInputs` after construction. In normal usage you should prefer the
|
|
21
|
+
* higher-level cache strategy layers instead of using this layer directly:
|
|
22
|
+
* - {@link AutoNodeCacheLive} — API with fallback to bundled defaults
|
|
23
|
+
* - {@link FreshNodeCacheLive} — API only, fails if unavailable
|
|
24
|
+
* - {@link OfflineNodeCacheLive} — bundled defaults only
|
|
25
|
+
*
|
|
26
|
+
* @see {@link NodeReleaseCache}
|
|
27
|
+
* @see {@link createRuntimeCache}
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
const NodeReleaseCacheLive = Layer.effect(NodeReleaseCache, Effect.gen(function* () {
|
|
31
|
+
const scheduleRef = yield* Ref.make(NodeSchedule.fromData({}));
|
|
32
|
+
const inner = yield* createRuntimeCache();
|
|
33
|
+
let currentInputs = [];
|
|
34
|
+
const buildAndLoad = (inputs) => Effect.gen(function* () {
|
|
35
|
+
const releases = [];
|
|
36
|
+
for (const input of inputs) {
|
|
37
|
+
const release = yield* NodeRelease.fromInput(input, scheduleRef).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
38
|
+
if (release) releases.push(release);
|
|
39
|
+
}
|
|
40
|
+
yield* inner.load(releases);
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
load: (releases) => inner.load(releases),
|
|
44
|
+
resolve: (range) => inner.resolve(range),
|
|
45
|
+
releases: () => inner.releases(),
|
|
46
|
+
filter: (range) => inner.filter(range),
|
|
47
|
+
latest: () => inner.latest(),
|
|
48
|
+
latestByMajor: () => inner.latestByMajor(),
|
|
49
|
+
latestByMinor: () => inner.latestByMinor(),
|
|
50
|
+
updateSchedule: (data) => Effect.gen(function* () {
|
|
51
|
+
yield* Ref.set(scheduleRef, NodeSchedule.fromData(data));
|
|
52
|
+
if (currentInputs.length > 0) yield* buildAndLoad(currentInputs);
|
|
53
|
+
}),
|
|
54
|
+
loadFromInputs: (inputs) => Effect.gen(function* () {
|
|
55
|
+
currentInputs = inputs;
|
|
56
|
+
yield* buildAndLoad(inputs);
|
|
57
|
+
}),
|
|
58
|
+
ltsReleases: (now) => Effect.gen(function* () {
|
|
59
|
+
const all = yield* inner.releases();
|
|
60
|
+
const results = [];
|
|
61
|
+
for (const r of all) {
|
|
62
|
+
const phase = yield* r.phase(now);
|
|
63
|
+
if (phase === "active-lts" || phase === "maintenance-lts") results.push(r);
|
|
64
|
+
}
|
|
65
|
+
return results;
|
|
66
|
+
}),
|
|
67
|
+
currentReleases: (now) => Effect.gen(function* () {
|
|
68
|
+
const all = yield* inner.releases();
|
|
69
|
+
const results = [];
|
|
70
|
+
for (const r of all) if ((yield* r.phase(now)) === "current") results.push(r);
|
|
71
|
+
return results;
|
|
72
|
+
})
|
|
73
|
+
};
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { NodeReleaseCacheLive };
|
|
78
|
+
//# sourceMappingURL=NodeReleaseCacheLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeReleaseCacheLive.js","names":[],"sources":["../../../../src/layers/NodeReleaseCacheLive.ts"],"sourcesContent":["import type { DateTime } from \"effect\";\nimport { Effect, Layer, Ref } from \"effect\";\nimport type { VersionCache as SemVerVersionCache } from \"semver-effect\";\nimport type { NodeReleaseInput } from \"../schemas/node-release.js\";\nimport { NodeRelease } from \"../schemas/node-release.js\";\nimport type { NodeScheduleData } from \"../schemas/node-schedule.js\";\nimport { NodeSchedule } from \"../schemas/node-schedule.js\";\nimport { NodeReleaseCache } from \"../services/NodeReleaseCache.js\";\nimport { createRuntimeCache } from \"./RuntimeCacheLive.js\";\n\n/**\n * Low-level layer that constructs the {@link NodeReleaseCache} service backed by\n * a `SemVerVersionCache` instance from `semver-effect`.\n *\n * This layer is more complex than the Bun and Deno equivalents because it manages\n * additional LTS schedule state via an internal `Ref`. The schedule is stored\n * separately from the version list so that calling `updateSchedule` can\n * transparently rebuild all release objects with the latest phase information\n * without reloading data from the network.\n *\n * This layer wires up the cache storage but does not populate it with any\n * release data. Callers are responsible for calling `updateSchedule` and\n * `loadFromInputs` after construction. In normal usage you should prefer the\n * higher-level cache strategy layers instead of using this layer directly:\n * - {@link AutoNodeCacheLive} — API with fallback to bundled defaults\n * - {@link FreshNodeCacheLive} — API only, fails if unavailable\n * - {@link OfflineNodeCacheLive} — bundled defaults only\n *\n * @see {@link NodeReleaseCache}\n * @see {@link createRuntimeCache}\n * @internal\n */\nexport const NodeReleaseCacheLive: Layer.Layer<NodeReleaseCache, never, SemVerVersionCache> = Layer.effect(\n\tNodeReleaseCache,\n\tEffect.gen(function* () {\n\t\tconst scheduleRef = yield* Ref.make(NodeSchedule.fromData({}));\n\t\tconst inner = yield* createRuntimeCache<NodeRelease>();\n\n\t\t// Held separately so we can rebuild releases when schedule updates\n\t\tlet currentInputs: ReadonlyArray<NodeReleaseInput> = [];\n\n\t\tconst buildAndLoad = (inputs: ReadonlyArray<NodeReleaseInput>) =>\n\t\t\tEffect.gen(function* () {\n\t\t\t\tconst releases: NodeRelease[] = [];\n\t\t\t\tfor (const input of inputs) {\n\t\t\t\t\tconst release = yield* NodeRelease.fromInput(input, scheduleRef).pipe(\n\t\t\t\t\t\tEffect.catchAll(() => Effect.succeed(null)),\n\t\t\t\t\t);\n\t\t\t\t\tif (release) releases.push(release);\n\t\t\t\t}\n\t\t\t\tyield* inner.load(releases);\n\t\t\t});\n\n\t\treturn {\n\t\t\t// -- RuntimeCache<NodeRelease> delegation --\n\t\t\tload: (releases) => inner.load(releases),\n\t\t\tresolve: (range) => inner.resolve(range),\n\t\t\treleases: () => inner.releases(),\n\t\t\tfilter: (range) => inner.filter(range),\n\t\t\tlatest: () => inner.latest(),\n\t\t\tlatestByMajor: () => inner.latestByMajor(),\n\t\t\tlatestByMinor: () => inner.latestByMinor(),\n\n\t\t\t// -- Node-specific --\n\t\t\tupdateSchedule: (data: NodeScheduleData) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tyield* Ref.set(scheduleRef, NodeSchedule.fromData(data));\n\t\t\t\t\t// Rebuild releases if we have stored inputs\n\t\t\t\t\tif (currentInputs.length > 0) {\n\t\t\t\t\t\tyield* buildAndLoad(currentInputs);\n\t\t\t\t\t}\n\t\t\t\t}),\n\n\t\t\tloadFromInputs: (inputs: ReadonlyArray<NodeReleaseInput>) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tcurrentInputs = inputs;\n\t\t\t\t\tyield* buildAndLoad(inputs);\n\t\t\t\t}),\n\n\t\t\tltsReleases: (now?: DateTime.DateTime) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst all = yield* inner.releases();\n\t\t\t\t\tconst results: NodeRelease[] = [];\n\t\t\t\t\tfor (const r of all) {\n\t\t\t\t\t\tconst phase = yield* r.phase(now);\n\t\t\t\t\t\tif (phase === \"active-lts\" || phase === \"maintenance-lts\") {\n\t\t\t\t\t\t\tresults.push(r);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn results;\n\t\t\t\t}),\n\n\t\t\tcurrentReleases: (now?: DateTime.DateTime) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst all = yield* inner.releases();\n\t\t\t\t\tconst results: NodeRelease[] = [];\n\t\t\t\t\tfor (const r of all) {\n\t\t\t\t\t\tconst phase = yield* r.phase(now);\n\t\t\t\t\t\tif (phase === \"current\") {\n\t\t\t\t\t\t\tresults.push(r);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn results;\n\t\t\t\t}),\n\t\t};\n\t}),\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,MAAa,uBAAiF,MAAM,OACnG,kBACA,OAAO,IAAI,aAAa;CACvB,MAAM,cAAc,OAAO,IAAI,KAAK,aAAa,SAAS,CAAC,CAAC,CAAC;CAC7D,MAAM,QAAQ,OAAO,mBAAgC;CAGrD,IAAI,gBAAiD,CAAC;CAEtD,MAAM,gBAAgB,WACrB,OAAO,IAAI,aAAa;EACvB,MAAM,WAA0B,CAAC;EACjC,KAAK,MAAM,SAAS,QAAQ;GAC3B,MAAM,UAAU,OAAO,YAAY,UAAU,OAAO,WAAW,CAAC,CAAC,KAChE,OAAO,eAAe,OAAO,QAAQ,IAAI,CAAC,CAC3C;GACA,IAAI,SAAS,SAAS,KAAK,OAAO;EACnC;EACA,OAAO,MAAM,KAAK,QAAQ;CAC3B,CAAC;CAEF,OAAO;EAEN,OAAO,aAAa,MAAM,KAAK,QAAQ;EACvC,UAAU,UAAU,MAAM,QAAQ,KAAK;EACvC,gBAAgB,MAAM,SAAS;EAC/B,SAAS,UAAU,MAAM,OAAO,KAAK;EACrC,cAAc,MAAM,OAAO;EAC3B,qBAAqB,MAAM,cAAc;EACzC,qBAAqB,MAAM,cAAc;EAGzC,iBAAiB,SAChB,OAAO,IAAI,aAAa;GACvB,OAAO,IAAI,IAAI,aAAa,aAAa,SAAS,IAAI,CAAC;GAEvD,IAAI,cAAc,SAAS,GAC1B,OAAO,aAAa,aAAa;EAEnC,CAAC;EAEF,iBAAiB,WAChB,OAAO,IAAI,aAAa;GACvB,gBAAgB;GAChB,OAAO,aAAa,MAAM;EAC3B,CAAC;EAEF,cAAc,QACb,OAAO,IAAI,aAAa;GACvB,MAAM,MAAM,OAAO,MAAM,SAAS;GAClC,MAAM,UAAyB,CAAC;GAChC,KAAK,MAAM,KAAK,KAAK;IACpB,MAAM,QAAQ,OAAO,EAAE,MAAM,GAAG;IAChC,IAAI,UAAU,gBAAgB,UAAU,mBACvC,QAAQ,KAAK,CAAC;GAEhB;GACA,OAAO;EACR,CAAC;EAEF,kBAAkB,QACjB,OAAO,IAAI,aAAa;GACvB,MAAM,MAAM,OAAO,MAAM,SAAS;GAClC,MAAM,UAAyB,CAAC;GAChC,KAAK,MAAM,KAAK,KAEf,KAAI,OADiB,EAAE,MAAM,GAAG,OAClB,WACb,QAAQ,KAAK,CAAC;GAGhB,OAAO;EACR,CAAC;CACH;AACD,CAAC,CACF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { NodeReleaseCache } from "../services/NodeReleaseCache.js";
|
|
2
|
+
import { VersionNotFoundError } from "../errors/VersionNotFoundError.js";
|
|
3
|
+
import { NodeResolver } from "../services/NodeResolver.js";
|
|
4
|
+
import { DateTime, Effect, Layer } from "effect";
|
|
5
|
+
import { SemVer } from "semver-effect";
|
|
6
|
+
|
|
7
|
+
//#region src/layers/NodeResolverLive.ts
|
|
8
|
+
/**
|
|
9
|
+
* Provides the {@link NodeResolver} service backed by a {@link NodeReleaseCache}.
|
|
10
|
+
*
|
|
11
|
+
* This layer composes with any of the three Node cache strategy layers to form
|
|
12
|
+
* a complete resolver stack. In addition to semver range filtering and increment
|
|
13
|
+
* grouping, it supports filtering by Node.js release phase (e.g., `"current"`,
|
|
14
|
+
* `"active-lts"`, `"maintenance-lts"`, `"end-of-life"`) and automatically
|
|
15
|
+
* determines the current LTS version from the loaded schedule data.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* import {
|
|
20
|
+
* NodeResolverLive, AutoNodeCacheLive,
|
|
21
|
+
* NodeVersionFetcherLive, NodeScheduleFetcherLive,
|
|
22
|
+
* GitHubClientLive, GitHubAutoAuth,
|
|
23
|
+
* } from "runtime-resolver";
|
|
24
|
+
* import { NodeResolver } from "runtime-resolver";
|
|
25
|
+
* import { Effect, Layer } from "effect";
|
|
26
|
+
*
|
|
27
|
+
* const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));
|
|
28
|
+
* const FetchersLayer = Layer.merge(
|
|
29
|
+
* NodeVersionFetcherLive.pipe(Layer.provide(GitHubLayer)),
|
|
30
|
+
* NodeScheduleFetcherLive.pipe(Layer.provide(GitHubLayer)),
|
|
31
|
+
* );
|
|
32
|
+
* const CacheLayer = AutoNodeCacheLive.pipe(Layer.provide(FetchersLayer));
|
|
33
|
+
* const ResolverLayer = NodeResolverLive.pipe(Layer.provide(CacheLayer));
|
|
34
|
+
*
|
|
35
|
+
* const program = Effect.gen(function* () {
|
|
36
|
+
* const resolver = yield* NodeResolver;
|
|
37
|
+
* // Resolve only active-LTS and maintenance-LTS releases in the ^20 range
|
|
38
|
+
* const result = yield* resolver.resolve({
|
|
39
|
+
* semverRange: "^20.0.0",
|
|
40
|
+
* phases: ["active-lts", "maintenance-lts"],
|
|
41
|
+
* });
|
|
42
|
+
* console.log(result.lts);
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @see {@link NodeResolver}
|
|
49
|
+
* @see {@link NodeReleaseCache}
|
|
50
|
+
* @see {@link AutoNodeCacheLive}
|
|
51
|
+
* @see {@link FreshNodeCacheLive}
|
|
52
|
+
* @see {@link OfflineNodeCacheLive}
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
55
|
+
const NodeResolverLive = Layer.effect(NodeResolver, Effect.gen(function* () {
|
|
56
|
+
const cache = yield* NodeReleaseCache;
|
|
57
|
+
return { resolve: (options) => Effect.gen(function* () {
|
|
58
|
+
const semverRange = options?.semverRange ?? "*";
|
|
59
|
+
const phases = options?.phases ?? ["current", "active-lts"];
|
|
60
|
+
const increments = options?.increments ?? "latest";
|
|
61
|
+
const now = options?.date ? DateTime.unsafeMake(options.date) : DateTime.unsafeMake(/* @__PURE__ */ new Date());
|
|
62
|
+
const matching = yield* cache.filter(semverRange).pipe(Effect.catchAll(() => Effect.succeed([])));
|
|
63
|
+
const phaseFiltered = [];
|
|
64
|
+
for (const r of matching) {
|
|
65
|
+
const phase = yield* r.phase(now);
|
|
66
|
+
if (phase && phases.includes(phase)) phaseFiltered.push(r);
|
|
67
|
+
}
|
|
68
|
+
let resultReleases;
|
|
69
|
+
if (increments === "latest") {
|
|
70
|
+
const groups = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const r of phaseFiltered) {
|
|
72
|
+
const existing = groups.get(r.version.major);
|
|
73
|
+
if (!existing || SemVer.gt(r.version, existing.version)) groups.set(r.version.major, r);
|
|
74
|
+
}
|
|
75
|
+
resultReleases = [...groups.values()];
|
|
76
|
+
} else if (increments === "minor") {
|
|
77
|
+
const groups = /* @__PURE__ */ new Map();
|
|
78
|
+
for (const r of phaseFiltered) {
|
|
79
|
+
const key = `${r.version.major}.${r.version.minor}`;
|
|
80
|
+
const existing = groups.get(key);
|
|
81
|
+
if (!existing || SemVer.gt(r.version, existing.version)) groups.set(key, r);
|
|
82
|
+
}
|
|
83
|
+
resultReleases = [...groups.values()];
|
|
84
|
+
} else resultReleases = phaseFiltered;
|
|
85
|
+
if (resultReleases.length === 0) return yield* Effect.fail(new VersionNotFoundError({
|
|
86
|
+
runtime: "node",
|
|
87
|
+
constraint: semverRange,
|
|
88
|
+
message: `No Node.js versions found matching "${semverRange}" with phases [${phases.join(", ")}]`
|
|
89
|
+
}));
|
|
90
|
+
const versions = SemVer.rsort(resultReleases.map((r) => r.version)).map((v) => resultReleases.find((r) => SemVer.equal(r.version, v))).filter((r) => r !== void 0).map((r) => r.version.toString());
|
|
91
|
+
const latest = versions[0];
|
|
92
|
+
const ltsVersions = (yield* cache.ltsReleases(now)).filter((r) => versions.includes(r.version.toString())).map((r) => r.version);
|
|
93
|
+
const lts = ltsVersions.length > 0 ? SemVer.rsort(ltsVersions)[0].toString() : void 0;
|
|
94
|
+
let resolvedDefault;
|
|
95
|
+
if (options?.defaultVersion) resolvedDefault = yield* cache.resolve(options.defaultVersion).pipe(Effect.map((r) => r.version.toString()), Effect.catchAll(() => Effect.succeed(void 0)));
|
|
96
|
+
return {
|
|
97
|
+
source: "api",
|
|
98
|
+
versions,
|
|
99
|
+
latest,
|
|
100
|
+
...lts ? { lts } : {},
|
|
101
|
+
...resolvedDefault ? { default: resolvedDefault } : { ...lts ? { default: lts } : {} }
|
|
102
|
+
};
|
|
103
|
+
}) };
|
|
104
|
+
}));
|
|
105
|
+
|
|
106
|
+
//#endregion
|
|
107
|
+
export { NodeResolverLive };
|
|
108
|
+
//# sourceMappingURL=NodeResolverLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeResolverLive.js","names":[],"sources":["../../../../src/layers/NodeResolverLive.ts"],"sourcesContent":["import { DateTime, Effect, Layer } from \"effect\";\nimport { SemVer } from \"semver-effect\";\nimport { VersionNotFoundError } from \"../errors/VersionNotFoundError.js\";\nimport type { Increments, NodePhase } from \"../schemas/common.js\";\nimport type { NodeRelease } from \"../schemas/node-release.js\";\nimport { NodeReleaseCache } from \"../services/NodeReleaseCache.js\";\nimport type { NodeResolverOptions } from \"../services/NodeResolver.js\";\nimport { NodeResolver } from \"../services/NodeResolver.js\";\n\n/**\n * Provides the {@link NodeResolver} service backed by a {@link NodeReleaseCache}.\n *\n * This layer composes with any of the three Node cache strategy layers to form\n * a complete resolver stack. In addition to semver range filtering and increment\n * grouping, it supports filtering by Node.js release phase (e.g., `\"current\"`,\n * `\"active-lts\"`, `\"maintenance-lts\"`, `\"end-of-life\"`) and automatically\n * determines the current LTS version from the loaded schedule data.\n *\n * @example\n * ```ts\n * import {\n * NodeResolverLive, AutoNodeCacheLive,\n * NodeVersionFetcherLive, NodeScheduleFetcherLive,\n * GitHubClientLive, GitHubAutoAuth,\n * } from \"runtime-resolver\";\n * import { NodeResolver } from \"runtime-resolver\";\n * import { Effect, Layer } from \"effect\";\n *\n * const GitHubLayer = GitHubClientLive.pipe(Layer.provide(GitHubAutoAuth));\n * const FetchersLayer = Layer.merge(\n * NodeVersionFetcherLive.pipe(Layer.provide(GitHubLayer)),\n * NodeScheduleFetcherLive.pipe(Layer.provide(GitHubLayer)),\n * );\n * const CacheLayer = AutoNodeCacheLive.pipe(Layer.provide(FetchersLayer));\n * const ResolverLayer = NodeResolverLive.pipe(Layer.provide(CacheLayer));\n *\n * const program = Effect.gen(function* () {\n * const resolver = yield* NodeResolver;\n * // Resolve only active-LTS and maintenance-LTS releases in the ^20 range\n * const result = yield* resolver.resolve({\n * semverRange: \"^20.0.0\",\n * phases: [\"active-lts\", \"maintenance-lts\"],\n * });\n * console.log(result.lts);\n * });\n *\n * Effect.runPromise(program.pipe(Effect.provide(ResolverLayer)));\n * ```\n *\n * @see {@link NodeResolver}\n * @see {@link NodeReleaseCache}\n * @see {@link AutoNodeCacheLive}\n * @see {@link FreshNodeCacheLive}\n * @see {@link OfflineNodeCacheLive}\n * @public\n */\nexport const NodeResolverLive: Layer.Layer<NodeResolver, never, NodeReleaseCache> = Layer.effect(\n\tNodeResolver,\n\tEffect.gen(function* () {\n\t\tconst cache = yield* NodeReleaseCache;\n\n\t\treturn {\n\t\t\tresolve: (options?: NodeResolverOptions) =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst semverRange = options?.semverRange ?? \"*\";\n\t\t\t\t\tconst phases: ReadonlyArray<NodePhase> = options?.phases ?? [\"current\", \"active-lts\"];\n\t\t\t\t\tconst increments: Increments = options?.increments ?? \"latest\";\n\t\t\t\t\tconst now = options?.date ? DateTime.unsafeMake(options.date) : DateTime.unsafeMake(new Date());\n\n\t\t\t\t\t// Get all releases matching range\n\t\t\t\t\tconst matching = yield* cache\n\t\t\t\t\t\t.filter(semverRange)\n\t\t\t\t\t\t.pipe(Effect.catchAll(() => Effect.succeed([] as ReadonlyArray<NodeRelease>)));\n\n\t\t\t\t\t// Filter by phase\n\t\t\t\t\tconst phaseFiltered: NodeRelease[] = [];\n\t\t\t\t\tfor (const r of matching) {\n\t\t\t\t\t\tconst phase = yield* r.phase(now);\n\t\t\t\t\t\tif (phase && phases.includes(phase)) {\n\t\t\t\t\t\t\tphaseFiltered.push(r);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply increments\n\t\t\t\t\tlet resultReleases: NodeRelease[];\n\t\t\t\t\tif (increments === \"latest\") {\n\t\t\t\t\t\tconst groups = new Map<number, NodeRelease>();\n\t\t\t\t\t\tfor (const r of phaseFiltered) {\n\t\t\t\t\t\t\tconst existing = groups.get(r.version.major);\n\t\t\t\t\t\t\tif (!existing || SemVer.gt(r.version, existing.version)) {\n\t\t\t\t\t\t\t\tgroups.set(r.version.major, r);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresultReleases = [...groups.values()];\n\t\t\t\t\t} else if (increments === \"minor\") {\n\t\t\t\t\t\tconst groups = new Map<string, NodeRelease>();\n\t\t\t\t\t\tfor (const r of phaseFiltered) {\n\t\t\t\t\t\t\tconst key = `${r.version.major}.${r.version.minor}`;\n\t\t\t\t\t\t\tconst existing = groups.get(key);\n\t\t\t\t\t\t\tif (!existing || SemVer.gt(r.version, existing.version)) {\n\t\t\t\t\t\t\t\tgroups.set(key, r);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresultReleases = [...groups.values()];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tresultReleases = phaseFiltered;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (resultReleases.length === 0) {\n\t\t\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\t\t\tnew VersionNotFoundError({\n\t\t\t\t\t\t\t\truntime: \"node\",\n\t\t\t\t\t\t\t\tconstraint: semverRange,\n\t\t\t\t\t\t\t\tmessage: `No Node.js versions found matching \"${semverRange}\" with phases [${phases.join(\", \")}]`,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Sort descending\n\t\t\t\t\tconst sorted = SemVer.rsort(resultReleases.map((r) => r.version));\n\t\t\t\t\tconst sortedReleases = sorted\n\t\t\t\t\t\t.map((v) => resultReleases.find((r) => SemVer.equal(r.version, v)))\n\t\t\t\t\t\t.filter((r): r is NodeRelease => r !== undefined);\n\n\t\t\t\t\tconst versions = sortedReleases.map((r) => r.version.toString());\n\t\t\t\t\tconst latest = versions[0];\n\n\t\t\t\t\t// Determine LTS\n\t\t\t\t\tconst ltsReleases = yield* cache.ltsReleases(now);\n\t\t\t\t\tconst ltsVersions = ltsReleases.filter((r) => versions.includes(r.version.toString())).map((r) => r.version);\n\t\t\t\t\tconst lts = ltsVersions.length > 0 ? SemVer.rsort(ltsVersions)[0].toString() : undefined;\n\n\t\t\t\t\t// Handle default version\n\t\t\t\t\tlet resolvedDefault: string | undefined;\n\t\t\t\t\tif (options?.defaultVersion) {\n\t\t\t\t\t\tresolvedDefault = yield* cache.resolve(options.defaultVersion).pipe(\n\t\t\t\t\t\t\tEffect.map((r) => r.version.toString()),\n\t\t\t\t\t\t\tEffect.catchAll(() => Effect.succeed(undefined)),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\treturn {\n\t\t\t\t\t\tsource: \"api\" as const,\n\t\t\t\t\t\tversions,\n\t\t\t\t\t\tlatest,\n\t\t\t\t\t\t...(lts ? { lts } : {}),\n\t\t\t\t\t\t...(resolvedDefault ? { default: resolvedDefault } : { ...(lts ? { default: lts } : {}) }),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t};\n\t}),\n);\n"],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { NodeScheduleFetcher } from "../services/NodeScheduleFetcher.js";
|
|
2
|
+
import { GitHubClient } from "../services/GitHubClient.js";
|
|
3
|
+
import { ParseError } from "../errors/ParseError.js";
|
|
4
|
+
import { NodeReleaseSchedule } from "../schemas/node.js";
|
|
5
|
+
import { Effect, Layer, Schema } from "effect";
|
|
6
|
+
|
|
7
|
+
//#region src/layers/NodeScheduleFetcherLive.ts
|
|
8
|
+
const NODE_SCHEDULE_URL = "https://raw.githubusercontent.com/nodejs/Release/refs/heads/main/schedule.json";
|
|
9
|
+
const decodeSchedule = (input) => Schema.decodeUnknown(NodeReleaseSchedule)(input).pipe(Effect.mapError((e) => new ParseError({
|
|
10
|
+
source: NODE_SCHEDULE_URL,
|
|
11
|
+
message: `Schema validation failed: ${e.message}`
|
|
12
|
+
})), Effect.map((data) => data));
|
|
13
|
+
/**
|
|
14
|
+
* Provides the {@link NodeScheduleFetcher} service using the live Node.js release schedule.
|
|
15
|
+
*
|
|
16
|
+
* Fetches the Node.js release schedule from the raw GitHub content URL for
|
|
17
|
+
* `nodejs/Release` (`schedule.json` on the `main` branch) using the HTTP client
|
|
18
|
+
* on the provided {@link GitHubClient} service. The schedule data maps major
|
|
19
|
+
* version codenames to their start, LTS, and end-of-life dates and is used by
|
|
20
|
+
* {@link NodeReleaseCache} to compute per-release lifecycle phases.
|
|
21
|
+
*
|
|
22
|
+
* This layer is a required dependency of {@link AutoNodeCacheLive} and
|
|
23
|
+
* {@link FreshNodeCacheLive}. It is not needed by {@link OfflineNodeCacheLive}.
|
|
24
|
+
*
|
|
25
|
+
* @see {@link NodeScheduleFetcher}
|
|
26
|
+
* @see {@link GitHubClient}
|
|
27
|
+
* @see {@link AutoNodeCacheLive}
|
|
28
|
+
* @see {@link FreshNodeCacheLive}
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
const NodeScheduleFetcherLive = Layer.effect(NodeScheduleFetcher, Effect.gen(function* () {
|
|
32
|
+
const client = yield* GitHubClient;
|
|
33
|
+
return { fetch: () => client.getJson(NODE_SCHEDULE_URL, { decode: decodeSchedule }) };
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
//#endregion
|
|
37
|
+
export { NodeScheduleFetcherLive };
|
|
38
|
+
//# sourceMappingURL=NodeScheduleFetcherLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeScheduleFetcherLive.js","names":[],"sources":["../../../../src/layers/NodeScheduleFetcherLive.ts"],"sourcesContent":["import { Effect, Layer, Schema } from \"effect\";\nimport { ParseError } from \"../errors/ParseError.js\";\nimport { NodeReleaseSchedule } from \"../schemas/node.js\";\nimport type { NodeScheduleData } from \"../schemas/node-schedule.js\";\nimport { GitHubClient } from \"../services/GitHubClient.js\";\nimport { NodeScheduleFetcher } from \"../services/NodeScheduleFetcher.js\";\n\nconst NODE_SCHEDULE_URL = \"https://raw.githubusercontent.com/nodejs/Release/refs/heads/main/schedule.json\";\n\nconst decodeSchedule = (input: unknown): Effect.Effect<NodeScheduleData, ParseError> =>\n\tSchema.decodeUnknown(NodeReleaseSchedule)(input).pipe(\n\t\tEffect.mapError(\n\t\t\t(e) =>\n\t\t\t\tnew ParseError({\n\t\t\t\t\tsource: NODE_SCHEDULE_URL,\n\t\t\t\t\tmessage: `Schema validation failed: ${e.message}`,\n\t\t\t\t}),\n\t\t),\n\t\tEffect.map((data) => data as NodeScheduleData),\n\t);\n\n/**\n * Provides the {@link NodeScheduleFetcher} service using the live Node.js release schedule.\n *\n * Fetches the Node.js release schedule from the raw GitHub content URL for\n * `nodejs/Release` (`schedule.json` on the `main` branch) using the HTTP client\n * on the provided {@link GitHubClient} service. The schedule data maps major\n * version codenames to their start, LTS, and end-of-life dates and is used by\n * {@link NodeReleaseCache} to compute per-release lifecycle phases.\n *\n * This layer is a required dependency of {@link AutoNodeCacheLive} and\n * {@link FreshNodeCacheLive}. It is not needed by {@link OfflineNodeCacheLive}.\n *\n * @see {@link NodeScheduleFetcher}\n * @see {@link GitHubClient}\n * @see {@link AutoNodeCacheLive}\n * @see {@link FreshNodeCacheLive}\n * @public\n */\nexport const NodeScheduleFetcherLive: Layer.Layer<NodeScheduleFetcher, never, GitHubClient> = Layer.effect(\n\tNodeScheduleFetcher,\n\tEffect.gen(function* () {\n\t\tconst client = yield* GitHubClient;\n\t\treturn {\n\t\t\tfetch: () => client.getJson(NODE_SCHEDULE_URL, { decode: decodeSchedule }),\n\t\t};\n\t}),\n);\n"],"mappings":";;;;;;;AAOA,MAAM,oBAAoB;AAE1B,MAAM,kBAAkB,UACvB,OAAO,cAAc,mBAAmB,CAAC,CAAC,KAAK,CAAC,CAAC,KAChD,OAAO,UACL,MACA,IAAI,WAAW;CACd,QAAQ;CACR,SAAS,6BAA6B,EAAE;AACzC,CAAC,CACH,GACA,OAAO,KAAK,SAAS,IAAwB,CAC9C;;;;;;;;;;;;;;;;;;;AAoBD,MAAa,0BAAiF,MAAM,OACnG,qBACA,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CACtB,OAAO,EACN,aAAa,OAAO,QAAQ,mBAAmB,EAAE,QAAQ,eAAe,CAAC,EAC1E;AACD,CAAC,CACF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NodeVersionFetcher } from "../services/NodeVersionFetcher.js";
|
|
2
|
+
import { tryParseSemVer } from "../lib/semver.js";
|
|
3
|
+
import { GitHubClient } from "../services/GitHubClient.js";
|
|
4
|
+
import { ParseError } from "../errors/ParseError.js";
|
|
5
|
+
import { NodeDistIndex } from "../schemas/node.js";
|
|
6
|
+
import { Effect, Layer, Option, Schema } from "effect";
|
|
7
|
+
|
|
8
|
+
//#region src/layers/NodeVersionFetcherLive.ts
|
|
9
|
+
const NODE_DIST_URL = "https://nodejs.org/dist/index.json";
|
|
10
|
+
const decodeDistIndex = (input) => Schema.decodeUnknown(NodeDistIndex)(input).pipe(Effect.mapError((e) => new ParseError({
|
|
11
|
+
source: NODE_DIST_URL,
|
|
12
|
+
message: `Schema validation failed: ${e.message}`
|
|
13
|
+
})));
|
|
14
|
+
/**
|
|
15
|
+
* Provides the {@link NodeVersionFetcher} service using the live Node.js dist index.
|
|
16
|
+
*
|
|
17
|
+
* Fetches the full Node.js version list from `https://nodejs.org/dist/index.json`
|
|
18
|
+
* using the HTTP client on the provided {@link GitHubClient} service. Version
|
|
19
|
+
* strings are normalized by stripping the leading `v` before parsing as semver.
|
|
20
|
+
* Entries with non-string `npm` fields (present in very old releases prior to
|
|
21
|
+
* v0.6.3) are substituted with the sentinel version `"0.0.0"`.
|
|
22
|
+
*
|
|
23
|
+
* This layer is a required dependency of {@link AutoNodeCacheLive} and
|
|
24
|
+
* {@link FreshNodeCacheLive}. It is not needed by {@link OfflineNodeCacheLive}.
|
|
25
|
+
*
|
|
26
|
+
* @see {@link NodeVersionFetcher}
|
|
27
|
+
* @see {@link GitHubClient}
|
|
28
|
+
* @see {@link AutoNodeCacheLive}
|
|
29
|
+
* @see {@link FreshNodeCacheLive}
|
|
30
|
+
* @public
|
|
31
|
+
*/
|
|
32
|
+
const NodeVersionFetcherLive = Layer.effect(NodeVersionFetcher, Effect.gen(function* () {
|
|
33
|
+
const client = yield* GitHubClient;
|
|
34
|
+
return { fetch: () => Effect.gen(function* () {
|
|
35
|
+
const allVersions = yield* client.getJson(NODE_DIST_URL, { decode: decodeDistIndex });
|
|
36
|
+
const parsed = yield* Effect.forEach(allVersions, (entry) => {
|
|
37
|
+
const clean = entry.version.replace(/^v/, "");
|
|
38
|
+
return tryParseSemVer(clean).pipe(Effect.map((opt) => Option.isSome(opt) ? Option.some({
|
|
39
|
+
version: opt.value,
|
|
40
|
+
input: {
|
|
41
|
+
version: clean,
|
|
42
|
+
npm: typeof entry.npm === "string" ? entry.npm : "0.0.0",
|
|
43
|
+
date: entry.date
|
|
44
|
+
}
|
|
45
|
+
}) : Option.none()));
|
|
46
|
+
}, { concurrency: "unbounded" });
|
|
47
|
+
return {
|
|
48
|
+
versions: parsed.flatMap(Option.toArray).map(({ version }) => version),
|
|
49
|
+
inputs: parsed.flatMap(Option.toArray).map(({ input }) => input)
|
|
50
|
+
};
|
|
51
|
+
}) };
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { NodeVersionFetcherLive };
|
|
56
|
+
//# sourceMappingURL=NodeVersionFetcherLive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodeVersionFetcherLive.js","names":[],"sources":["../../../../src/layers/NodeVersionFetcherLive.ts"],"sourcesContent":["import { Effect, Layer, Option, Schema } from \"effect\";\nimport { ParseError } from \"../errors/ParseError.js\";\nimport { tryParseSemVer } from \"../lib/semver.js\";\nimport { NodeDistIndex } from \"../schemas/node.js\";\nimport type { NodeReleaseInput } from \"../schemas/node-release.js\";\nimport { GitHubClient } from \"../services/GitHubClient.js\";\nimport { NodeVersionFetcher } from \"../services/NodeVersionFetcher.js\";\n\nconst NODE_DIST_URL = \"https://nodejs.org/dist/index.json\";\n\nconst decodeDistIndex = (input: unknown) =>\n\tSchema.decodeUnknown(NodeDistIndex)(input).pipe(\n\t\tEffect.mapError(\n\t\t\t(e) =>\n\t\t\t\tnew ParseError({\n\t\t\t\t\tsource: NODE_DIST_URL,\n\t\t\t\t\tmessage: `Schema validation failed: ${e.message}`,\n\t\t\t\t}),\n\t\t),\n\t);\n\n/**\n * Provides the {@link NodeVersionFetcher} service using the live Node.js dist index.\n *\n * Fetches the full Node.js version list from `https://nodejs.org/dist/index.json`\n * using the HTTP client on the provided {@link GitHubClient} service. Version\n * strings are normalized by stripping the leading `v` before parsing as semver.\n * Entries with non-string `npm` fields (present in very old releases prior to\n * v0.6.3) are substituted with the sentinel version `\"0.0.0\"`.\n *\n * This layer is a required dependency of {@link AutoNodeCacheLive} and\n * {@link FreshNodeCacheLive}. It is not needed by {@link OfflineNodeCacheLive}.\n *\n * @see {@link NodeVersionFetcher}\n * @see {@link GitHubClient}\n * @see {@link AutoNodeCacheLive}\n * @see {@link FreshNodeCacheLive}\n * @public\n */\nexport const NodeVersionFetcherLive: Layer.Layer<NodeVersionFetcher, never, GitHubClient> = Layer.effect(\n\tNodeVersionFetcher,\n\tEffect.gen(function* () {\n\t\tconst client = yield* GitHubClient;\n\n\t\treturn {\n\t\t\tfetch: () =>\n\t\t\t\tEffect.gen(function* () {\n\t\t\t\t\tconst allVersions = yield* client.getJson(NODE_DIST_URL, {\n\t\t\t\t\t\tdecode: decodeDistIndex,\n\t\t\t\t\t});\n\n\t\t\t\t\tconst parsed = yield* Effect.forEach(\n\t\t\t\t\t\tallVersions,\n\t\t\t\t\t\t(entry) => {\n\t\t\t\t\t\t\tconst clean = entry.version.replace(/^v/, \"\");\n\t\t\t\t\t\t\treturn tryParseSemVer(clean).pipe(\n\t\t\t\t\t\t\t\tEffect.map((opt) =>\n\t\t\t\t\t\t\t\t\tOption.isSome(opt)\n\t\t\t\t\t\t\t\t\t\t? Option.some({\n\t\t\t\t\t\t\t\t\t\t\t\tversion: opt.value,\n\t\t\t\t\t\t\t\t\t\t\t\tinput: {\n\t\t\t\t\t\t\t\t\t\t\t\t\tversion: clean,\n\t\t\t\t\t\t\t\t\t\t\t\t\t// Older entries (pre-v0.6.3) carry npm:false. Use \"0.0.0\" as a sentinel\n\t\t\t\t\t\t\t\t\t\t\t\t\t// since npm version is only informational and never used in comparisons.\n\t\t\t\t\t\t\t\t\t\t\t\t\tnpm: typeof entry.npm === \"string\" ? entry.npm : \"0.0.0\",\n\t\t\t\t\t\t\t\t\t\t\t\t\tdate: entry.date,\n\t\t\t\t\t\t\t\t\t\t\t\t} satisfies NodeReleaseInput,\n\t\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t\t: Option.none(),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ concurrency: \"unbounded\" },\n\t\t\t\t\t);\n\n\t\t\t\t\tconst versions = parsed.flatMap(Option.toArray).map(({ version }) => version);\n\t\t\t\t\tconst inputs = parsed.flatMap(Option.toArray).map(({ input }) => input);\n\n\t\t\t\t\treturn { versions, inputs };\n\t\t\t\t}),\n\t\t};\n\t}),\n);\n"],"mappings":";;;;;;;;AAQA,MAAM,gBAAgB;AAEtB,MAAM,mBAAmB,UACxB,OAAO,cAAc,aAAa,CAAC,CAAC,KAAK,CAAC,CAAC,KAC1C,OAAO,UACL,MACA,IAAI,WAAW;CACd,QAAQ;CACR,SAAS,6BAA6B,EAAE;AACzC,CAAC,CACH,CACD;;;;;;;;;;;;;;;;;;;AAoBD,MAAa,yBAA+E,MAAM,OACjG,oBACA,OAAO,IAAI,aAAa;CACvB,MAAM,SAAS,OAAO;CAEtB,OAAO,EACN,aACC,OAAO,IAAI,aAAa;EACvB,MAAM,cAAc,OAAO,OAAO,QAAQ,eAAe,EACxD,QAAQ,gBACT,CAAC;EAED,MAAM,SAAS,OAAO,OAAO,QAC5B,cACC,UAAU;GACV,MAAM,QAAQ,MAAM,QAAQ,QAAQ,MAAM,EAAE;GAC5C,OAAO,eAAe,KAAK,CAAC,CAAC,KAC5B,OAAO,KAAK,QACX,OAAO,OAAO,GAAG,IACd,OAAO,KAAK;IACZ,SAAS,IAAI;IACb,OAAO;KACN,SAAS;KAGT,KAAK,OAAO,MAAM,QAAQ,WAAW,MAAM,MAAM;KACjD,MAAM,MAAM;IACb;GACD,CAAC,IACA,OAAO,KAAK,CAChB,CACD;EACD,GACA,EAAE,aAAa,YAAY,CAC5B;EAKA,OAAO;GAAE,UAHQ,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,EAAE,cAAc,OAGrD;GAAG,QAFJ,OAAO,QAAQ,OAAO,OAAO,CAAC,CAAC,KAAK,EAAE,YAAY,KAEzC;EAAE;CAC3B,CAAC,EACH;AACD,CAAC,CACF"}
|