@ effect/ github- 动作
GitHub 动作运行时间 效果 v4:一个动作需要与其内部执行的跑者交谈的服务. Action.run 编译运行时间,运行您的程序, 使失败为 ::error:: 注释和设置退出代码. ActionInput 将工作流程输入读取为输入 Config 值 —— 字符串、布尔、整数、已编辑的秘密、多行列表, key=value 双对,或一个被解码的JSON blob——每个访问者共享一个缺出规则:未设置和 "" 缺少数据. ActionOutputs, (中文(简体) ), ActionState, (中文(简体) ), ActionLogger 和纤维-局部 ActionEnvironment 绕出跑道的地表 ActionCache, (中文(简体) ), Artifact,元数据传输 BlobStore (S3相容或跑者自己的缓存), OidcTokenIssuer 财务报告和审定财务报表 ToolInstaller 对于更重的协议。 报告套件—— GitHubMarkdown, (中文(简体) ), ManagedDocument, (中文(简体) ), CheckState 财务报告和审定财务报表 CheckDocument ——涵盖另一个方向:将运行结果渲染到拉动请求注释上,检查运行或工作摘要上. 没有 @actions/* 任何地方的依赖性——缓存,文物和工具缓存协议直接针对他们的HTTP API执行.
预
1.0.0。 。 。 . 此软件包是@effected/*在稳定基础上建造的工具包 效应v4(effect~4.1.0) 并仍在0.x发展。 稳定效果 制作一个工具箱1.0.0可能,不是自动的。 为了保持你的effect财务报告和审定财务报表@effect/*该工具包在线上构建和测试、安装@effected/pnpm-plugin-effected。 。 。 .稳定:不稳定. 此软件包的 API 表面尚未考虑 完成并可能改变
0.x释放 定一个确切的版本——甚至 标记软件包 稳定 在此之前1.0.0可以引入突破性更改 和精确的针 变成一个类型检查错误 而不是一个 跑步时间惊喜。 完全政策: 释放策略。 。 。 .
为什么
@actions/core 及其兄弟姐妹的权利@actions/cache, (中文(简体) ), @actions/artifact, (中文(简体) ), @actions/tool-cache)是写作动作的标准方式,每个动作都拥有自己的临时合同: 输入用 getInput返回时 "" 用于“ unset” 和“ 设置为空” 且无法在类型级别将它们区分开来; 输出、 遮盖和工作流程命令是独立的全局; 缓存和文物客户端会带回自己的 HTTP 堆栈 。 这套软件包回答的问题与一套连贯的效应服务相同,缺出规则一次说明(Config.withDefault 是每个可选输入的平面图),并且每个突变都运行在相同的 R- 类型服务 一个测试可以互换.
这也是 包里有个地方 地点 @effect/platform-node 是需要的对等方, 刻意: GitHub Action 总是在 GitHub 提供的跑道上编译成节点进程, 因此没有第二个平台可以抽象化 。 所有的东西 能够 完成核心合同还是.. ToolInstaller 下载结束 HttpClient 并提取过来 ChildProcessSpawner, (中文(简体) ), CacheKey 读取 FileSystem ——被处罚的四人 node: 进口量node:crypto 为文摘, process.kill 以裸食者发誓, node:child_process 为离子而产出 node:zlib编号 :node:stream 在缓存和文物解码器中)完全涵盖了核心尚不能做的.
线对 @effected/github 准确的说: 包对 GitHub API , 这个和跑者说话。 他们在两个接合处相会——标志桥(SGitHubToken)和 Logger 地图 Effect.log* 输入工作流程命令 —— 这里没有读到 process.env.GITHUB_REPOSITORY 打开 github代表着.
安装
npm install @effected/github-actions effect @effect/platform-node
pnpm add @effected/github-actions effect @effect/platform-node
需要节点.js~~24.11.0. effect 页:1 @effect/platform-node 两者都是同伴的依赖关系.
全体 @effected/* 软件包仅为无害环境管理:只公布出口地图 import 条件,所以 require() 包括解析 CJS 模式的工具 —— 与节点失败 ERR_PACKAGE_PATH_NOT_EXPORTED 而不是装入不存在的 CJS 建筑。 从ES模块导入.
快开始
import { Action, ActionInput, ActionOutputs } from "@effected/github-actions";
import { Config, Effect } from "effect";
const program = Effect.gen(function* () {
const name = yield* ActionInput.string("name").pipe(Config.withDefault("world"));
const outputs = yield* ActionOutputs;
yield* outputs.set("greeting", `Hello, ${name}!`);
});
await Action.run(program);
// exit code reflects success/failure; failures render as an ::error:: annotation
Action.run 提供 ActionServices (环境、日志、产出、状态、节点平台,a) HttpClient 编辑 Digest, (中文(简体) ), Mac, (中文(简体) ), SecureRandom 财务报告和审定财务报表 Signature 默认加密服务). 具有较重依赖性的能力——缓存、文物、blob商店——是另外一行: Action.run(program, { layer: ActionCache.layer })。 。 。 .
步骤调试工作不接线: 当跑者设定时 RUNNER_DEBUG=1, (中文(简体) ), Action.run 将效果最小日志级别降低到 Debug这样 Effect.logDebug 电话显示为 ::debug:: 线条。 它只会降低水平,通过 { stepDebugLogLevel: false } 保持原样.
读取输入
每个访问者都有一个缺勤规则: 跑者写道: "" 对于一个输入,工作流程省略了,这个软件包读取的是,与未设置相同—— 缺少数据,不是空值。 可选输入需求 Config.withDefault (或 Config.option在呼叫地点:
import { ActionInput } from "@effected/github-actions";
import { Config, Effect } from "effect";
const program = Effect.gen(function* () {
const dryRun = yield* ActionInput.boolean("dry-run").pipe(Config.withDefault(false));
const paths = yield* ActionInput.list("paths").pipe(Config.withDefault([]));
return { dryRun, paths };
});
// dryRun: boolean; paths: readonly string[] — [] when the input was omitted
list 接受 JSON 阵列、 圆形列表或逗号和新行分隔值, 并带有全行 # 注释被丢弃——以工作流程作者达到的形状为准。 A 类 已存在但结构不全 价值(dry-run: yes 而不是YAML 1.2 bolean)是不同于一个缺位的类: 它没有携带它的 actual 绝不是被默认的.
标志桥
GitHubToken gitHub 形状 应用认证 就像它运行在内部的 三相工作流程: 薄荷入 pre,在 main,在 post阶段之间除了什么 GITHUB_STATE 因此,一个装置标志——大约一个小时的装置——一直存在,而不是被保存在 Scope数字 :
import { GitHubToken } from "@effected/github-actions";
import { Effect, Redacted } from "effect";
// pre:
const pre = GitHubToken.provision({
appId: "123456",
privateKey: Redacted.make(process.env.APP_PRIVATE_KEY as string),
owner: "acme",
required: { contents: "write", pull_requests: "write" },
});
// main: bind once — layers memoize by reference
const ClientLayer = GitHubToken.clientLayer();
// post:
const post = GitHubToken.dispose();
GitHubToken.read 输入失败 reason: "expired" gitHub会用一个赤裸的401来回答问题, 能够重点一个的证书是应用程序的私人密钥 GITHUB_STATE 文件会用一个小时的代号换一个永久的.
秘诀
Secret 此包中唯一的位置 a Redacted 值变为平整的字符串,而蒙面和解密是同一个调用——每个成员都用跑者来登记值 ::add-mask:: 过滤器 在此之前 返回纯文本 :
import { Secret } from "@effected/github-actions";
import { Effect } from "effect";
declare const token: import("effect/Redacted").Redacted<string>;
const program = Effect.gen(function* () {
const env = yield* Secret.forChildEnv({ MY_TOKEN: token });
// every value in `env` is already masked in the runner log
return env;
});
跑者藏品、文物和OIDC
ActionCache 财务报告和审定财务报表 Artifact 直接说出动作 Twirp v2 协议 — no @actions/cache 或者说 @actions/artifact 依赖性,两者均被排除在 ActionRuntime.layer 因此,一个只设置输出的消费者从不链接 @azure/storage-blob数字 :
import { ActionCache, CacheKey } from "@effected/github-actions";
import { Effect, Option } from "effect";
const program = Effect.gen(function* () {
const cache = yield* ActionCache;
const key = CacheKey.of("Linux", "pnpm-store", "abc123");
const hit = yield* cache.restore(["~/.pnpm-store"], key);
if (Option.isNone(hit)) {
yield* cache.save(["~/.pnpm-store"], key);
}
});
await Action.run(program, { layer: ActionCache.layer });
CacheKey.withNamespace(segment) 是缓存- bust 大小写 —— 运行必须匹配所写的未中断的运行, 并且其自己的条目必须隐蔽到未中断的运行 :
import { CacheKey } from "@effected/github-actions";
const key = CacheKey.of("Linux", "pnpm-store", "abc123").withNamespace("bust7");
console.log(key.key, key.restoreKeys);
// bust7-Linux-pnpm-store-abc123 []
两项决定使得这个对于任何分值都是安全的,在呼叫站点没有前缀推理. 片段开始 第一个,因此命名空格密钥没有与未命名空格共享前缀——一个还原密钥是前缀匹配,在后缀中折叠出一个已断裂的已失效的普通跑符前缀匹配条目. 梯子是 丢弃,因此,即使片段恰好等于普通的领先片段,命名空间外也没有任何rung到达. 这是安全的默认而不是禁止: withRestoreDepths 一个故意的名阶梯.
CacheKey.hashFiles 与字节兼容 @actions/glob是因为 hashFiles,以及 OidcTokenIssuer.claims 解码跑者OIDC的指使为SLSA出处上游而未验证其签名——指使从跑者自己的指使服务中到达TLS上空,因此运输是信托边界,这里没有关于授权请求的分支.
ActionsProvenance.capture 将这些索赔变成 @effected/sbom SlsaProvenance 上游,和 ActionsIdentityToken.layer 服务 @effected/oidc狭窄 IdentityToken 包的签名人消费的合约,来自同一发行商的倒置——使Actions运行时间超过每个只想要放出SBOM的消费者:
import { ActionsIdentityToken, ActionsProvenance, OidcTokenIssuer } from "@effected/github-actions";
import { SigstoreSigner } from "@effected/sbom";
import { Effect, Layer } from "effect";
const program = Effect.gen(function* () {
const provenance = yield* ActionsProvenance.capture();
return provenance.runDetails.builder.id;
});
// the builder id, derived from the workflow ref in the runner's own claims
const SigningLayer = SigstoreSigner.layer.pipe(
Layer.provide(ActionsIdentityToken.layer),
Layer.provide(OidcTokenIssuer.layer),
);
一个 OidcTokenError 路过 capture 不受影响:是否允许未经测试的发布是行动的政策,而不是这个软件包. reason: "unavailable" 几乎总是意味着工作流程缺失 permissions: id-token: write。 。 。 .
关于 GitHub 表面的报告
一个动作的另一半的工作是说出发生了什么——在拉出请求的评论,检查运行的总结或工作总结. GitHubMarkdown 写那些尸体。 每个成员都采用预降标记并返回一个字符串,但周围的结构会经过 @effected/markdown序列器而不是字符串加入,所以带单元格 >=1 || <2 从管子上逃出 而不是从管子上移出. tableFor 再往前走,然后从一行图中取出桌子的形状:
import { GitHubMarkdown } from "@effected/github-actions";
import { Schema } from "effect";
const Row = Schema.Struct({
name: Schema.String.annotate({ title: "Check" }),
detail: Schema.String,
});
console.log(GitHubMarkdown.tableFor(Row).render([{ name: "build", detail: "clean" }]));
// | Check | detail |
// | --- | --- |
// | build | clean |
列顺序是字段声明命令,每个标题是字段的 title 注释,而每个单元格都是字段被编码的形式——所以一个被键入的字段工程通过自己的编码器,一行不能转出列,因为它是对象而不是位置阵列.
ManagedDocument 在文本中拥有一个人类也编辑的指定区域:一个Sentinel HTML 注释可以识别文档,标记注释可以划分每个区域,而它们以外的一切都能活到字节。 区域从目前的状态取而代之,而不是附加的状态,这就使得粘糊糊的评语能够安全地重新提交. CheckDocument 在顶端设置一个解开的调和器——当检查状态发生变化时报告一次检查,一个背景纤维将登记册投放到文档上,并通过一个狭长的汇来写入:
import { CheckDocument, CheckReport } from "@effected/github-actions";
import { Effect } from "effect";
const program = Effect.gen(function* () {
const doc = yield* CheckDocument;
yield* doc.report("build", CheckReport.make({ state: "running" }));
yield* doc.report("build", CheckReport.make({ state: "pass", outcome: "clean" }));
return yield* doc.flush;
});
// "written" — one write carrying the final state
// "unchanged" — the render was byte-identical to the last one, so nothing was sent
// "stale" — a newer run owns the document; this pass was dropped (see below)
解弹的后方有一个最大等待, 所以一阵报告 汇合到一个写着 最终 状态,而平稳的溪流仍然会浮出水面.
一个区域还可以携带元数据: withRegions 带 [key, content, meta] 三相,两对搭乘区域自己的标志 entry(key) 逐字读取。 元数据能活下来的作家 财务报告和审定财务报表 它的元数据——从不参与处理一个区域的问题,因此改变它,更新它的存在,而不是增加第二个区域.
CheckDocument 精确地用它来活过两个运行写出一个文档——重复运行它所取代的延迟运行. 给这层 stamp 和能够 read 以及 write,每个传球都与活文件相对应,而不是这个过程最后写到的私人阴影:
import { CheckDocument } from "@effected/github-actions";
import type { Effect } from "effect";
declare const startedAt: string;
declare const runId: string;
declare const readComment: Effect.Effect<string | undefined>;
declare const writeComment: (text: string) => Effect.Effect<void>;
const layer = CheckDocument.layer({
namespace: "release-bot",
key: "checks",
render: (checks) => [...checks].map(([name, report]) => [name, report.state] as const),
sink: { write: writeComment, read: readComment },
stamp: { at: startedAt, runId }, // a per-run constant — mint it once at startup
});
每一个地区都写着 at 财务报告和审定财务报表 runId而不是写下来.. flush 答题 "stale" 调和器在过渡时记录掉一次。 平分的邮票通行证,所以跑步总是可以完善自己的区域. 保持印章常数是保存书写压制的:同样的使同态的运行仍然产生字节-同位文字.
CheckState 是这两个词的词汇, 它被故意比 GitHub 的结论集更广 running 是一个国家,而不是一个不存在的国家 user_interaction_required 给等待人类的管道起名. projectCheckState gitHub 想要将每个图都映射到线上, 作为状态加结论, 确切的状态是 completed。 。 。 .
类型失败
此套件中的四个失败类型是 标记一个错误等级的结合 而不是一个带有 reason 字段 : ActionOutputError, (中文(简体) ), BlobEnvelopeError, (中文(简体) ), CacheKeyError 财务报告和审定财务报表 DetachedProcessError这些名称中的每一个名称都仍然存在,仍然意味着同一组结果,所以没有改变签名——现在每个名称都是其成员的别名:
| 工会 | 成员 |
|---|---|
ActionOutputError | RunnerFileUnavailableError, (中文(简体) ), RunnerFileWriteError, (中文(简体) ), InvalidOutputNameError, (中文(简体) ), OutputEncodeError, (中文(简体) ), DetachedOutputError |
BlobEnvelopeError | NotABlobEnvelopeError, (中文(简体) ), TruncatedBlobEnvelopeError, (中文(简体) ), UnsupportedBlobEnvelopeVersionError, (中文(简体) ), BlobMetadataDecodeError, (中文(简体) ), BlobMetadataEncodeError |
CacheKeyError | CacheKeyReadError, (中文(简体) ), CacheKeyBadPatternError |
DetachedProcessError | DetachedLogUnavailableError, (中文(简体) ), DetachedSpawnFailedError, (中文(简体) ), InvalidPidError, (中文(简体) ), DetachedSignalFailedError, (中文(简体) ), DetachedNotReadyError |
出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出. 代码的分支 error.reason 匹配打开 _tag 换句话说,这就是买单的原因:一个失败可以自己处理 Effect.catchTag,其余的则停留在错误频道中,而不是被只意在回答其中一个的处理器所抓住.
import { CacheKey } from "@effected/github-actions";
import { Effect, Option } from "effect";
const digest = CacheKey.hashMatching({ workspace: ".", patterns: ["**/pnpm-lock.yaml"] }).pipe(
Effect.catchTag("CacheKeyBadPatternError", () => Effect.succeed(Option.none<string>())),
);
// Effect<Option<string>, CacheKeyReadError, FileSystem | Path>
// a bad pattern becomes "no digest"; a read failure stays in the error channel
测试
每艘服务船 makeTest(overrides?) 财务报告和审定财务报表 layerTest(overrides?)3个有记录的例外说明它们为何违约: ActionEnvironment.makeTest 种子十二 GITHUB_*编号 :RUNNER_* 变量, ActionLogger.makeTest 默认沉默,和 DryRun.makeTest 默认为排练,安全方向. ActionEnvironment.makeTest编号 :layerTest 同时将webhook事件有效载荷作为可选的第二个参数,直接为它服务,而不是通过一个 GITHUB_EVENT_PATH 文件系统读取被 stubbed 文件系统永远无法满足 :
import { ActionEnvironment } from "@effected/github-actions";
const layer = ActionEnvironment.layerTest(
{ GITHUB_EVENT_NAME: "pull_request" },
{ pull_request: { number: 42 } },
);
import { ActionOutputs } from "@effected/github-actions";
import { Effect } from "effect";
const TestOutputs = ActionOutputs.layerTest({
set: () => Effect.void,
});
OidcTokenIssuer.layerFor(claims) 返回从同一索赔中构建的可解码的 JWT claims() 报告,以及 BlobStore.layerMemory 运行真正的信封框架——两者都存在,因为之前的合成双倍使得出处路径结构上无法测试.
特征
Action.run编号 :ActionRuntime.layer—— 切入点和默认运行时间:环境、日志、输出、状态、节点平台和一个HttpClient。 。 。 .ActionInput- 输入存取器(string, (中文(简体) ),boolean, (中文(简体) ),integer, (中文(简体) ),redacted, (中文(简体) ),lines, (中文(简体) ),list, (中文(简体) ),pairs, (中文(简体) ),schema共享一个缺勤规则,加上INPUT_-意识到ConfigProviderAction.run默认安装.pairs总是拒绝空密钥( n)=value破坏的陆地远离字典)requireValue拒绝空值.ActionInput.recording(env)输入程序读取的日记ActionManifest.diff。 。 。 .ActionManifest—— 说action.yml已解码( e)read, (中文(简体) ),decode, (中文(简体) ),decodeResult:带有默认、输出、品牌和节点、复合或插头的投入runs块.runnerInputs建立输入环境 一个真正的跑者为节点或挂起器动作所构建(每个在节点或挂起器下宣布的输入)INPUT_变量,包含提供的值,其显示的默认值或"",加上任何未申报的供货名称,如跑道所通过)作为输入测试的基准diff报告输入和输出代码和显示的分歧.ActionOutputs- 步骤产出、JSON产出、工作摘要、输出变量、,PATH添加、失败说明和日志掩码.ActionState编号 :ActionEnvironment——坚持pre编号 :main编号 :post状态,和纤维局部GITHUB_*编号 :RUNNER_*上下文读取一次process.env。 。 。 .ActionLogger——相撞组(意识意识:跑者不能筑巢组,所以在另一组内打开的组写道:▸ <name>标题行代替第二组命令,缓冲步骤记录(withBuffer({ onSuccess: "discard" })完全放下绿色台阶的笔录, 而失败,缺陷或中断仍然冲去它,withStep(name, effect, options?)同一缓冲加一行关于成功和❌ <name>关于失败的记录上方的头条,::notice::说明,以及Logger使每个Effect.log*作为工作流程命令.DryRun- 排练后,每个突变都通过, 驱动的dry-run默认输入.Secret- 一个解密的接合器Redacted值和一个平面字符串,在返回平面文字之前先遮盖.GitHubToken- App-token 生命周期形状像pre编号 :main编号 :post活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活活.ActionCache编号 :Artifact——跑者在Twirp v2上的缓存和文物协议被排除在默认运行时间之外,这样一个跳过它们的消费者就永远不会链接到Azure.CacheKey缓存键及其还原键梯,withNamespace(segment)用于破损的标志或共享运行器前缀,加hashFiles编号 :matchingFiles编号 :hashMatching字节兼容@actions/glob。 。 。 .OidcTokenIssuer- 跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑快跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑跑recording(claims)是观众要求的日记的两倍.ActionsRegistryCredential—— 说workload({ audience, prefix })薄荷用于注册的身份标志, 遮盖赤色和前缀值, 并返回@effected/npm仅发布WorkloadCredential页:1PackagePublish.setupAuth。 。 。 .ActionsIdentityToken编号 :ActionsProvenance- 那个@effected/sbom连接 :layer服务@effected/oidc是因为IdentityToken跑道发行人的合同,以及capture构建一个SlsaProvenance跑者OIDC索赔的上游.GitHubMarkdownGitHub 的逃生安全编剧:table, (中文(简体) ),tableFor(schema), (中文(简体) ),heading, (中文(简体) ),link, (中文(简体) ),code, (中文(简体) ),codeBlock, (中文(简体) ),list, (中文(简体) ),details财务报告和审定财务报表raw,该包是唯一进口@effected/markdown。 。 。 .ManagedDocument——在文本中标记-限定命名区域 人类也编辑,用于粘贴评论或管理下拉请求描述;区域外的内容活过字节取字节,每个区域可以携带来回行走的标记元数据entry(key)从未提及过该地区.CheckState编号 :projectCheckState——工具包的检查词汇,比GitHub的结论更宽,以及它投射到检查运行的电线上.CheckDocument——解封的对账调节器:报告在变化时表示,一个写有最后一块的土地.stamp加上一个可读的槽 让两个运行共享一个文档, 投出一个严格老运行的通过 而不是打更新的一个;flush报告written, (中文(简体) ),unchanged或者说stale。 。 。 .ToolInstaller——下载,取出并缓存跑者工具缓存中的工具链,无@actions/tool-cache依赖性;ToolInstallerError总是命名它的subject。 。 。 .BlobStore- 持久get编号 :put编号 :has结束StoredBlob<A>值(字节加调用器-schema元数据;类型被命名)Blob<A>之前,这与全球冲突),由layerS3(西格维4-通过@effected/crypto,所以它需要核心Digest.Digest(单位:千美元)effect/crypto(单位:千美元)R,哪个ActionRuntime.layer提供;否@aws-sdk/*扶养)或GitHubCacheBlobStore.layer(跑者自己的缓存);layerMemory运行真正的信封框架进行测试.