@effected/github-actions 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Action.js +138 -0
- package/ActionCache.js +317 -0
- package/ActionEnvironment.js +255 -0
- package/ActionInput.js +346 -0
- package/ActionLogger.js +205 -0
- package/ActionOutputs.js +135 -0
- package/ActionState.js +117 -0
- package/Artifact.js +423 -0
- package/BlobEnvelope.js +135 -0
- package/BlobStore.githubCache.js +183 -0
- package/BlobStore.js +203 -0
- package/BlobTransfer.js +21 -0
- package/CacheKey.js +234 -0
- package/DetachedProcess.js +231 -0
- package/DryRun.js +73 -0
- package/GitHubToken.js +226 -0
- package/LICENSE +21 -0
- package/OidcTokenIssuer.js +246 -0
- package/Secret.js +106 -0
- package/ToolInstaller.js +293 -0
- package/WorkflowCommand.js +100 -0
- package/index.d.ts +2467 -0
- package/index.js +22 -0
- package/internal/actionsResults.js +98 -0
- package/internal/sigv4.js +117 -0
- package/internal/twirp.js +97 -0
- package/package.json +52 -0
- package/tsdoc-metadata.json +11 -0
package/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ActionEnvironment, ActionEnvironmentError, GitHubContext, RunnerContext } from "./ActionEnvironment.js";
|
|
2
|
+
import { ActionInput } from "./ActionInput.js";
|
|
3
|
+
import { WorkflowCommand } from "./WorkflowCommand.js";
|
|
4
|
+
import { ActionLogger } from "./ActionLogger.js";
|
|
5
|
+
import { ActionOutputError, ActionOutputs } from "./ActionOutputs.js";
|
|
6
|
+
import { ActionState, ActionStateError } from "./ActionState.js";
|
|
7
|
+
import { Action, ActionRuntime, describeCause } from "./Action.js";
|
|
8
|
+
import { BlobTransferError } from "./BlobTransfer.js";
|
|
9
|
+
import { ActionCache, ActionCacheError } from "./ActionCache.js";
|
|
10
|
+
import { Artifact, ArtifactError } from "./Artifact.js";
|
|
11
|
+
import { BlobEnvelope, BlobEnvelopeError } from "./BlobEnvelope.js";
|
|
12
|
+
import { Secret } from "./Secret.js";
|
|
13
|
+
import { BlobStore, BlobStoreError } from "./BlobStore.js";
|
|
14
|
+
import { GitHubCacheBlobStore } from "./BlobStore.githubCache.js";
|
|
15
|
+
import { CacheKey, CacheKeyError } from "./CacheKey.js";
|
|
16
|
+
import { DetachedProcess, DetachedProcessError, ProcessId } from "./DetachedProcess.js";
|
|
17
|
+
import { DryRun } from "./DryRun.js";
|
|
18
|
+
import { GitHubToken, GitHubTokenError } from "./GitHubToken.js";
|
|
19
|
+
import { OidcClaims, OidcTokenError, OidcTokenIssuer } from "./OidcTokenIssuer.js";
|
|
20
|
+
import { ToolInstaller, ToolInstallerError } from "./ToolInstaller.js";
|
|
21
|
+
|
|
22
|
+
export { Action, ActionCache, ActionCacheError, ActionEnvironment, ActionEnvironmentError, ActionInput, ActionLogger, ActionOutputError, ActionOutputs, ActionRuntime, ActionState, ActionStateError, Artifact, ArtifactError, BlobEnvelope, BlobEnvelopeError, BlobStore, BlobStoreError, BlobTransferError, CacheKey, CacheKeyError, DetachedProcess, DetachedProcessError, DryRun, GitHubCacheBlobStore, GitHubContext, GitHubToken, GitHubTokenError, OidcClaims, OidcTokenError, OidcTokenIssuer, ProcessId, RunnerContext, Secret, ToolInstaller, ToolInstallerError, WorkflowCommand, describeCause };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Effect, Option, Redacted, Result } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/actionsResults.ts
|
|
4
|
+
/**
|
|
5
|
+
* Reading the runner's *results backend* coordinates — the two variables the
|
|
6
|
+
* cache, artifact and blob-store protocols all speak through.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* Shared by `ActionCache`, `Artifact` and `BlobStore.githubCache`, and
|
|
10
|
+
* deliberately free of `@azure/storage-blob`: an internal helper is exactly how
|
|
11
|
+
* a heavy import leaks into a light module's graph, so the one piece those
|
|
12
|
+
* three modules share is the piece with no dependencies.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
/** The variable naming the backend. Absent outside a `uses:` step. */
|
|
17
|
+
const RESULTS_URL = "ACTIONS_RESULTS_URL";
|
|
18
|
+
/** The variable holding the run-scoped credential. Absent outside a `uses:` step. */
|
|
19
|
+
const RUNTIME_TOKEN = "ACTIONS_RUNTIME_TOKEN";
|
|
20
|
+
/**
|
|
21
|
+
* Decode a JWT payload segment.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* No signature verification, deliberately and for the same reason
|
|
25
|
+
* `OidcTokenIssuer` gives: the token was handed to this process by the runner
|
|
26
|
+
* that started it, and the claim read here scopes a request rather than
|
|
27
|
+
* authorizing one.
|
|
28
|
+
*/
|
|
29
|
+
const payloadOf = (token) => {
|
|
30
|
+
const segments = token.split(".");
|
|
31
|
+
if (segments.length !== 3) throw new Error(`expected a three-segment JWT, got ${segments.length}`);
|
|
32
|
+
const json = Buffer.from(segments[1] ?? "", "base64url").toString("utf8");
|
|
33
|
+
return JSON.parse(json);
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The run and job ids the artifact protocol needs, from the runtime token's
|
|
37
|
+
* `scp` claim.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* The scope is space-separated and the interesting entry is
|
|
41
|
+
* `Actions.Results:<run>:<job>`. Exported so its own failures are tested
|
|
42
|
+
* directly rather than through four RPC round trips.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
45
|
+
*/
|
|
46
|
+
const backendIdsFrom = (token) => {
|
|
47
|
+
let payload;
|
|
48
|
+
try {
|
|
49
|
+
payload = payloadOf(token);
|
|
50
|
+
} catch (cause) {
|
|
51
|
+
return Result.fail(`the runtime token is not a readable JWT: ${cause instanceof Error ? cause.message : cause}`);
|
|
52
|
+
}
|
|
53
|
+
const scope = payload.scp;
|
|
54
|
+
if (typeof scope !== "string") return Result.fail("the runtime token carries no `scp` claim");
|
|
55
|
+
for (const entry of scope.split(" ")) {
|
|
56
|
+
const parts = entry.split(":");
|
|
57
|
+
if (parts[0] !== "Actions.Results") continue;
|
|
58
|
+
const run = parts[1];
|
|
59
|
+
const job = parts[2];
|
|
60
|
+
if (parts.length !== 3 || run === void 0 || run === "" || job === void 0 || job === "") return Result.fail(`the runtime token's Actions.Results scope is malformed: "${entry}"`);
|
|
61
|
+
return Result.succeed({
|
|
62
|
+
workflowRunBackendId: run,
|
|
63
|
+
workflowJobRunBackendId: job
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return Result.fail("the runtime token carries no `Actions.Results` scope");
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Read the results-backend coordinates from the runner environment.
|
|
70
|
+
*
|
|
71
|
+
* @remarks
|
|
72
|
+
* Fails with the *name* of whichever variable is missing. Both are injected
|
|
73
|
+
* only into `uses:` steps, so their absence is the single most common way one
|
|
74
|
+
* of these three services is misused — a caller that reads the name can say so.
|
|
75
|
+
*
|
|
76
|
+
* Takes the environment **shape**, not the service: the layer resolves
|
|
77
|
+
* `ActionEnvironment` once at construction and every member's `R` stays
|
|
78
|
+
* `never`, which is the same fix `ActionEnvironment.payload` got. The variables
|
|
79
|
+
* themselves are read per call, because resolving them at construction would
|
|
80
|
+
* make merely *composing* the layer fail outside Actions — including for an
|
|
81
|
+
* action that never touches the cache.
|
|
82
|
+
*
|
|
83
|
+
* @internal
|
|
84
|
+
*/
|
|
85
|
+
const resultsBackend = (env) => Effect.gen(function* () {
|
|
86
|
+
const url = yield* env.getOptional(RESULTS_URL);
|
|
87
|
+
const token = yield* env.getOptional(RUNTIME_TOKEN);
|
|
88
|
+
if (Option.isNone(url)) return yield* Effect.fail(RESULTS_URL);
|
|
89
|
+
if (Option.isNone(token)) return yield* Effect.fail(RUNTIME_TOKEN);
|
|
90
|
+
return {
|
|
91
|
+
baseUrl: url.value.endsWith("/") ? url.value : `${url.value}/`,
|
|
92
|
+
token: Redacted.make(token.value),
|
|
93
|
+
backendIds: backendIdsFrom(token.value)
|
|
94
|
+
};
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { RESULTS_URL, RUNTIME_TOKEN, backendIdsFrom, resultsBackend };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createHash, createHmac } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/sigv4.ts
|
|
4
|
+
/**
|
|
5
|
+
* AWS Signature Version 4, for S3-compatible object stores.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* **Pure**, and about a hundred lines, which is the whole argument for not
|
|
9
|
+
* taking an `@aws-sdk/*` dependency to obtain it. Signing is a specified
|
|
10
|
+
* algorithm over strings and HMACs; the SDK around it exists to manage
|
|
11
|
+
* credentials, retries and a service catalogue this package does not want.
|
|
12
|
+
*
|
|
13
|
+
* `node:crypto` rather than core `Crypto` because core's `Crypto` is
|
|
14
|
+
* random-number generation only at beta.101 — no digest, no HMAC. This package
|
|
15
|
+
* is the one place in the kit where a `node:` import is sanctioned, and this is
|
|
16
|
+
* one of the four reasons.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
const ALGORITHM = "AWS4-HMAC-SHA256";
|
|
21
|
+
const sha256Hex = (value) => createHash("sha256").update(value).digest("hex");
|
|
22
|
+
const hmac = (key, value) => new Uint8Array(createHmac("sha256", key).update(value).digest());
|
|
23
|
+
/**
|
|
24
|
+
* Percent-encode one path segment the way SigV4 requires.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* `encodeURIComponent` leaves `!'()*` alone and AWS does not, so a key
|
|
28
|
+
* containing any of them signs one string and requests another — which fails as
|
|
29
|
+
* an opaque `SignatureDoesNotMatch` rather than as anything that names the
|
|
30
|
+
* character. The unreserved set is exactly `A-Za-z0-9-_.~`.
|
|
31
|
+
*/
|
|
32
|
+
const uriEncode = (value) => encodeURIComponent(value).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
33
|
+
/** `20130524T000000Z` and `20130524`, the two forms the algorithm uses. */
|
|
34
|
+
const timestamps = (now) => {
|
|
35
|
+
const amzDate = `${now.toISOString().replace(/[:-]|\.\d{3}/g, "").slice(0, 15)}Z`;
|
|
36
|
+
return {
|
|
37
|
+
amzDate,
|
|
38
|
+
dateStamp: amzDate.slice(0, 8)
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Assemble the headers that will be signed, and the canonical request over them.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* Exported separately from {@link sign} so a test can pin it against AWS's
|
|
46
|
+
* **own documented** canonical request rather than against this
|
|
47
|
+
* implementation's output. That distinction is not pedantry: the first draft of
|
|
48
|
+
* this module was checked against a remembered signature constant that turned
|
|
49
|
+
* out to be from a different example, and the only thing that settled which side
|
|
50
|
+
* was wrong was reproducing a value AWS publishes.
|
|
51
|
+
*/
|
|
52
|
+
const canonicalize = (request, credentials) => {
|
|
53
|
+
const { amzDate, dateStamp } = timestamps(request.now);
|
|
54
|
+
const payloadHash = sha256Hex(request.body);
|
|
55
|
+
const headers = {
|
|
56
|
+
...request.headers,
|
|
57
|
+
host: request.host,
|
|
58
|
+
"x-amz-date": amzDate,
|
|
59
|
+
"x-amz-content-sha256": payloadHash,
|
|
60
|
+
...credentials.sessionToken === void 0 ? {} : { "x-amz-security-token": credentials.sessionToken }
|
|
61
|
+
};
|
|
62
|
+
const lowered = new Map(Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value]));
|
|
63
|
+
const canonicalNames = [...lowered.keys()].sort();
|
|
64
|
+
const canonicalHeaders = canonicalNames.map((name) => `${name}:${(lowered.get(name) ?? "").trim().replace(/\s+/g, " ")}\n`).join("");
|
|
65
|
+
const signedHeaders = canonicalNames.join(";");
|
|
66
|
+
const canonicalPath = `/${request.path.split("/").filter((segment) => segment !== "").map(uriEncode).join("/")}`;
|
|
67
|
+
return {
|
|
68
|
+
headers,
|
|
69
|
+
signedHeaders,
|
|
70
|
+
amzDate,
|
|
71
|
+
dateStamp,
|
|
72
|
+
canonicalRequest: [
|
|
73
|
+
request.method,
|
|
74
|
+
canonicalPath,
|
|
75
|
+
"",
|
|
76
|
+
canonicalHeaders,
|
|
77
|
+
signedHeaders,
|
|
78
|
+
payloadHash
|
|
79
|
+
].join("\n")
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Derive a signing key.
|
|
84
|
+
*
|
|
85
|
+
* @remarks
|
|
86
|
+
* Four nested HMACs over date, region, service and the literal `aws4_request`.
|
|
87
|
+
* Exported so a test can reproduce AWS's documented derivation example.
|
|
88
|
+
*/
|
|
89
|
+
const signingKey = (secretAccessKey, dateStamp, region, service) => hmac(hmac(hmac(hmac(`AWS4${secretAccessKey}`, dateStamp), region), service), "aws4_request");
|
|
90
|
+
/**
|
|
91
|
+
* Sign a request, returning the headers to send.
|
|
92
|
+
*
|
|
93
|
+
* @remarks
|
|
94
|
+
* The returned record is the caller's headers **plus** everything signing added
|
|
95
|
+
* — `host`, `x-amz-date`, `x-amz-content-sha256`, the optional
|
|
96
|
+
* `x-amz-security-token` and `authorization` — so a caller sends exactly what
|
|
97
|
+
* was signed rather than assembling a second, subtly different set.
|
|
98
|
+
*/
|
|
99
|
+
const sign = (request, credentials) => {
|
|
100
|
+
const { headers, canonicalRequest, signedHeaders, amzDate, dateStamp } = canonicalize(request, credentials);
|
|
101
|
+
const scope = `${dateStamp}/${credentials.region}/${credentials.service}/aws4_request`;
|
|
102
|
+
const stringToSign = [
|
|
103
|
+
ALGORITHM,
|
|
104
|
+
amzDate,
|
|
105
|
+
scope,
|
|
106
|
+
sha256Hex(canonicalRequest)
|
|
107
|
+
].join("\n");
|
|
108
|
+
const key = signingKey(credentials.secretAccessKey, dateStamp, credentials.region, credentials.service);
|
|
109
|
+
const signature = Buffer.from(hmac(key, stringToSign)).toString("hex");
|
|
110
|
+
return {
|
|
111
|
+
...headers,
|
|
112
|
+
authorization: `${ALGORITHM} Credential=${credentials.accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
export { canonicalize, sign, signingKey, uriEncode };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Effect, Schedule } from "effect";
|
|
2
|
+
import { HttpClientRequest } from "effect/unstable/http";
|
|
3
|
+
|
|
4
|
+
//#region src/internal/twirp.ts
|
|
5
|
+
/**
|
|
6
|
+
* Returned instead of a failure when the backend answers HTTP 409.
|
|
7
|
+
*
|
|
8
|
+
* @remarks
|
|
9
|
+
* A conflict means "this already exists", which is a success for a save and a
|
|
10
|
+
* miss for a lookup — two different answers that the *caller* has to choose
|
|
11
|
+
* between, so the transport hands back a sentinel rather than deciding.
|
|
12
|
+
*
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
const CONFLICT = Symbol.for("@effected/github-actions/twirp/conflict");
|
|
16
|
+
/**
|
|
17
|
+
* Whether retrying could plausibly help.
|
|
18
|
+
*
|
|
19
|
+
* @remarks
|
|
20
|
+
* A transport fault never completed, so nothing was observed; `408`, `429` and
|
|
21
|
+
* `5xx` are the backend saying "later". Everything else — a `400`, a `403`, a
|
|
22
|
+
* body that is not JSON — is the backend saying "never", and retrying it four
|
|
23
|
+
* times only makes a broken call take half a minute to fail.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
const isRetryable = (failure) => {
|
|
28
|
+
if (failure.kind === "transport") return true;
|
|
29
|
+
if (failure.kind === "malformed" || failure.status === void 0) return false;
|
|
30
|
+
return failure.status >= 500 || failure.status === 408 || failure.status === 429;
|
|
31
|
+
};
|
|
32
|
+
/** How many times a retryable RPC is retried before giving up. */
|
|
33
|
+
const RETRIES = 4;
|
|
34
|
+
/**
|
|
35
|
+
* A Twirp RPC: `POST <baseUrl>twirp/<service>/<method>` with a JSON body.
|
|
36
|
+
*
|
|
37
|
+
* @remarks
|
|
38
|
+
* Retry is applied **here**, not by each caller, so no protocol can be shipped
|
|
39
|
+
* without it. A non-retryable failure never sleeps, which is what keeps the
|
|
40
|
+
* ordinary failure tests clock-free.
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
const twirpCall = (options) => {
|
|
45
|
+
const { baseUrl, body, http, method, service, token } = options;
|
|
46
|
+
return Effect.gen(function* () {
|
|
47
|
+
const request = HttpClientRequest.post(`${baseUrl}twirp/${service}/${method}`).pipe(HttpClientRequest.bearerToken(token), HttpClientRequest.bodyJsonUnsafe(body));
|
|
48
|
+
const response = yield* http.execute(request).pipe(Effect.mapError((cause) => ({
|
|
49
|
+
method,
|
|
50
|
+
kind: "transport",
|
|
51
|
+
cause
|
|
52
|
+
})));
|
|
53
|
+
if (response.status === 409) return CONFLICT;
|
|
54
|
+
if (response.status < 200 || response.status >= 300) return yield* Effect.fail({
|
|
55
|
+
method,
|
|
56
|
+
kind: "status",
|
|
57
|
+
status: response.status
|
|
58
|
+
});
|
|
59
|
+
return yield* response.json.pipe(Effect.mapError((cause) => ({
|
|
60
|
+
method,
|
|
61
|
+
kind: "malformed",
|
|
62
|
+
cause
|
|
63
|
+
})));
|
|
64
|
+
}).pipe(Effect.retry({
|
|
65
|
+
schedule: Schedule.exponential("3 seconds", 1.5),
|
|
66
|
+
times: RETRIES,
|
|
67
|
+
while: isRetryable
|
|
68
|
+
}));
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Read a Twirp JSON field under either spelling.
|
|
72
|
+
*
|
|
73
|
+
* @remarks
|
|
74
|
+
* The backend is an internal GitHub protocol reverse-engineered from
|
|
75
|
+
* `actions/toolkit`, and the two halves of it do not agree: protobuf JSON emits
|
|
76
|
+
* `signedUploadUrl` while the cache RPCs have been observed emitting
|
|
77
|
+
* `signed_upload_url`. Reading both costs one function and removes a class of
|
|
78
|
+
* failure that presents as "the cache silently never hits".
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
const field = (body, name) => {
|
|
83
|
+
if (typeof body !== "object" || body === null) return;
|
|
84
|
+
const record = body;
|
|
85
|
+
const snake = name.replaceAll(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
86
|
+
return record[name] ?? record[snake];
|
|
87
|
+
};
|
|
88
|
+
/** {@link field}, as a string, or `undefined` when absent or empty. @internal */
|
|
89
|
+
const stringField = (body, name) => {
|
|
90
|
+
const value = field(body, name);
|
|
91
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
92
|
+
};
|
|
93
|
+
/** Whether a Twirp response carries `ok: true`. @internal */
|
|
94
|
+
const isOk = (body) => field(body, "ok") === true;
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { CONFLICT, field, isOk, isRetryable, stringField, twirpCall };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@effected/github-actions",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "The GitHub Actions runtime: env, inputs, outputs, state, logging, cache, artifacts, blob storage, OIDC and the token bridge.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"github-actions",
|
|
8
|
+
"actions",
|
|
9
|
+
"runtime",
|
|
10
|
+
"cache",
|
|
11
|
+
"oidc",
|
|
12
|
+
"effect",
|
|
13
|
+
"effected"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/spencerbeggs/effected/tree/main/packages/github-actions#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/spencerbeggs/effected/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/spencerbeggs/effected.git",
|
|
22
|
+
"directory": "packages/github-actions"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": {
|
|
26
|
+
"name": "C. Spencer Beggs",
|
|
27
|
+
"email": "spencer@beggs.codes",
|
|
28
|
+
"url": "https://spencerbeg.gs"
|
|
29
|
+
},
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"type": "module",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./index.d.ts",
|
|
35
|
+
"import": "./index.js",
|
|
36
|
+
"default": "./index.js"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@azure/storage-blob": "^12.33.0",
|
|
42
|
+
"@effected/github": "~0.1.0",
|
|
43
|
+
"@effected/glob": "~0.2.1"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@effect/platform-node": "4.0.0-beta.101",
|
|
47
|
+
"effect": "4.0.0-beta.101"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=24.11.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.58.12"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|