bitfab 0.64.4 → 0.64.6
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/cloudReplay.py +275 -22
- package/dist/{chunk-UMZDRAAK.js → chunk-6NUSTEQ6.js} +6 -6
- package/dist/{chunk-I6B5R2FP.js → chunk-B363XZTL.js} +2 -2
- package/dist/{chunk-OEHQPSOB.js → chunk-J342BFLH.js} +3 -3
- package/dist/{chunk-XASGXGQN.js → chunk-MS3GWCJB.js} +2 -2
- package/dist/{chunk-XASGXGQN.js.map → chunk-MS3GWCJB.js.map} +1 -1
- package/dist/{chunk-ZPVUSLIU.js → chunk-ROZEHSGO.js} +2 -2
- package/dist/{chunk-ZPVUSLIU.js.map → chunk-ROZEHSGO.js.map} +1 -1
- package/dist/{http-53RWIBXJ.js → http-2UZ6CM3K.js} +2 -2
- package/dist/{http-Y6T6T2AP.js → http-BD3WK3FL.js} +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/node.cjs +1 -1
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +3 -3
- package/dist/{replay-SIKHFPKA.js → replay-KQCLQRJT.js} +3 -3
- package/dist/replayCli.js +2 -2
- package/dist/seedCli.js +2 -2
- package/package.json +1 -1
- /package/dist/{chunk-UMZDRAAK.js.map → chunk-6NUSTEQ6.js.map} +0 -0
- /package/dist/{chunk-I6B5R2FP.js.map → chunk-B363XZTL.js.map} +0 -0
- /package/dist/{chunk-OEHQPSOB.js.map → chunk-J342BFLH.js.map} +0 -0
- /package/dist/{http-53RWIBXJ.js.map → http-2UZ6CM3K.js.map} +0 -0
- /package/dist/{http-Y6T6T2AP.js.map → http-BD3WK3FL.js.map} +0 -0
- /package/dist/{replay-SIKHFPKA.js.map → replay-KQCLQRJT.js.map} +0 -0
package/cloudReplay.py
CHANGED
|
@@ -34,6 +34,7 @@ REPLAY_COMMAND_ENV = "BITFAB_REPLAY_COMMAND"
|
|
|
34
34
|
SDK_LANGUAGE_ENV = "BITFAB_SDK_LANGUAGE"
|
|
35
35
|
CHECK_COMMAND_ENV = "BITFAB_REPLAY_CHECK"
|
|
36
36
|
REQUEST_VERSION = 3
|
|
37
|
+
OLDEST_DISPATCHED_VERSION = 2
|
|
37
38
|
RESULT_LINE = "bitfab-replay-result "
|
|
38
39
|
RESULT_CHUNK = 4000
|
|
39
40
|
OUTPUT_BEGIN = "bitfab-replay-output-begin"
|
|
@@ -87,14 +88,20 @@ ENVIRONMENT_SETTING = re.compile(
|
|
|
87
88
|
PUSH_TRIGGER = re.compile(
|
|
88
89
|
r"^[ \t]*\"?(?:on\"?[ \t]*:.*\bpush\b|push\"?[ \t]*:)", re.MULTILINE
|
|
89
90
|
)
|
|
90
|
-
|
|
91
|
+
DISPATCH_INPUTS = ("execution_id", "request")
|
|
92
|
+
BRANCH_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._/-]*")
|
|
91
93
|
FOLLOW_FLAGS = (
|
|
92
94
|
"--cloud-status",
|
|
93
95
|
"--cloud-watch",
|
|
94
96
|
"--cloud-cancel",
|
|
95
97
|
"--cloud-cleanup",
|
|
96
98
|
)
|
|
97
|
-
CLOUD_VALUE_FLAGS = (
|
|
99
|
+
CLOUD_VALUE_FLAGS = (
|
|
100
|
+
*FOLLOW_FLAGS,
|
|
101
|
+
"--cloud-request-id",
|
|
102
|
+
"--cloud-timeout",
|
|
103
|
+
"--cloud-ref",
|
|
104
|
+
)
|
|
98
105
|
CLOUD_SWITCHES = ("--cloud", "--cloud-preview", "--cloud-detach", "--cloud-full-output")
|
|
99
106
|
RENAMED_FLAGS = {
|
|
100
107
|
"--cloud-dry-run": "--cloud-preview",
|
|
@@ -144,6 +151,26 @@ Options added by --cloud:
|
|
|
144
151
|
GitHub job log holds.
|
|
145
152
|
--cloud-timeout MIN Stop the replay after MIN minutes (1..7200).
|
|
146
153
|
--cloud-request-id ID Recover a submission whose response was lost.
|
|
154
|
+
--cloud-ref BRANCH Replay BRANCH as it is on GitHub, with no snapshot of your
|
|
155
|
+
working tree, and record the experiment on BRANCH. Meant for
|
|
156
|
+
CI jobs, such as a nightly replay of dev. Needs only GitHub
|
|
157
|
+
access that can run Actions, and a workflow on BRANCH that
|
|
158
|
+
declares a request input.
|
|
159
|
+
|
|
160
|
+
A workflow can also dispatch bitfab-replay.yml itself on any branch other than a
|
|
161
|
+
bitfab-replay/ snapshot branch, passing the replay as JSON in the request input:
|
|
162
|
+
|
|
163
|
+
{"args": ["--registry", "scripts/replay.ts", "classify", "--dataset-ids", "UUID"],
|
|
164
|
+
"cwd": "web", "timeoutMinutes": 60}
|
|
165
|
+
|
|
166
|
+
args are the replay options without --cloud. cwd is the directory to replay from,
|
|
167
|
+
relative to the repository root, and defaults to the root. timeoutMinutes and
|
|
168
|
+
fullOutput (true) match --cloud-timeout and --cloud-full-output. version and id are
|
|
169
|
+
optional: version names the request format (3 now, 2 still accepted), and id is the
|
|
170
|
+
execution UUID, which must match an execution_id input when the workflow has one.
|
|
171
|
+
|
|
172
|
+
The runner's job exits with the replay's exit code, so the Actions run fails when
|
|
173
|
+
the replay does. Traces that errored fail it only under --fail-on-error.
|
|
147
174
|
|
|
148
175
|
Follow a replay by the execution UUID it prints:
|
|
149
176
|
--cloud-watch ID | --cloud-status ID | --cloud-cancel ID | --cloud-cleanup ID
|
|
@@ -611,6 +638,16 @@ def parse(argv):
|
|
|
611
638
|
timeout.isdigit() and 1 <= int(timeout) <= 7200
|
|
612
639
|
):
|
|
613
640
|
raise ValueError("--cloud-timeout must be 1..7200 minutes")
|
|
641
|
+
branch = cloud.get("--cloud-ref")
|
|
642
|
+
if branch is not None and (
|
|
643
|
+
not BRANCH_NAME.fullmatch(branch)
|
|
644
|
+
or branch.startswith(PREFIX)
|
|
645
|
+
or ".." in branch
|
|
646
|
+
or branch.endswith((".", "/", ".lock"))
|
|
647
|
+
):
|
|
648
|
+
raise ValueError(
|
|
649
|
+
f"--cloud-ref takes a branch name such as dev, other than a {PREFIX} snapshot branch"
|
|
650
|
+
)
|
|
614
651
|
operation = "submit"
|
|
615
652
|
execution_id = cloud.get("--cloud-request-id") or str(uuid.uuid4())
|
|
616
653
|
if not UUID.fullmatch(execution_id):
|
|
@@ -797,6 +834,8 @@ def execution_lock(directory, execution_id):
|
|
|
797
834
|
|
|
798
835
|
|
|
799
836
|
def find_run(record):
|
|
837
|
+
if record.get("snapshot") is False:
|
|
838
|
+
return find_branch_run(record)
|
|
800
839
|
query = urlencode(
|
|
801
840
|
{"event": "workflow_dispatch", "branch": record["branch"], "per_page": 100}
|
|
802
841
|
)
|
|
@@ -815,6 +854,31 @@ def find_run(record):
|
|
|
815
854
|
return matches[0] if matches else None
|
|
816
855
|
|
|
817
856
|
|
|
857
|
+
def find_branch_run(record):
|
|
858
|
+
query = urlencode(
|
|
859
|
+
{
|
|
860
|
+
"event": "workflow_dispatch",
|
|
861
|
+
"branch": record["branch"],
|
|
862
|
+
"created": ">=" + record["dispatchedAt"],
|
|
863
|
+
"per_page": 100,
|
|
864
|
+
}
|
|
865
|
+
)
|
|
866
|
+
runs = api(
|
|
867
|
+
record["repository"], f"actions/workflows/{record['workflow']}/runs?{query}"
|
|
868
|
+
)
|
|
869
|
+
matches = [
|
|
870
|
+
run
|
|
871
|
+
for run in runs["workflow_runs"]
|
|
872
|
+
if run["head_branch"] == record["branch"]
|
|
873
|
+
and run.get("created_at", "") >= record["dispatchedAt"]
|
|
874
|
+
]
|
|
875
|
+
if len(matches) > 1:
|
|
876
|
+
raise ValueError(
|
|
877
|
+
f"Several runs were dispatched on {record['branch']} at about the same time, so this execution cannot tell which is its own; find it in the Actions tab"
|
|
878
|
+
)
|
|
879
|
+
return matches[0] if matches else None
|
|
880
|
+
|
|
881
|
+
|
|
818
882
|
def status(record, *, fetch_result=True):
|
|
819
883
|
if record["state"] == "prepared":
|
|
820
884
|
return record
|
|
@@ -826,6 +890,8 @@ def status(record, *, fetch_result=True):
|
|
|
826
890
|
if run is None:
|
|
827
891
|
record["state"] = "dispatch_unknown"
|
|
828
892
|
return record
|
|
893
|
+
if record.get("snapshot") is False and run["head_branch"] == record["branch"]:
|
|
894
|
+
record["sha"] = run["head_sha"]
|
|
829
895
|
if run["head_sha"] != record["sha"] or run["head_branch"] != record["branch"]:
|
|
830
896
|
raise ValueError("GitHub run does not match the recorded execution")
|
|
831
897
|
record.update(
|
|
@@ -955,6 +1021,9 @@ def print_outcome(found):
|
|
|
955
1021
|
def cleanup(root, record):
|
|
956
1022
|
if record.get("cleaned"):
|
|
957
1023
|
return
|
|
1024
|
+
if record.get("snapshot") is False:
|
|
1025
|
+
record["cleaned"] = True
|
|
1026
|
+
return
|
|
958
1027
|
if record.get("state") not in ("completed", "prepared"):
|
|
959
1028
|
raise ValueError(
|
|
960
1029
|
"Cleanup requires a confirmed completed GitHub run; cancel and wait first"
|
|
@@ -983,7 +1052,7 @@ def http_status(error):
|
|
|
983
1052
|
return f" (HTTP {status})" if status else ""
|
|
984
1053
|
|
|
985
1054
|
|
|
986
|
-
def preflight(repo, workflow):
|
|
1055
|
+
def preflight(repo, workflow, *, pushes=True):
|
|
987
1056
|
access = github_access()
|
|
988
1057
|
try:
|
|
989
1058
|
details = api(repo, "")
|
|
@@ -991,7 +1060,7 @@ def preflight(repo, workflow):
|
|
|
991
1060
|
raise ValueError(
|
|
992
1061
|
f"GitHub access from {access['source']} cannot read {repo}{http_status(error)}; use an account or token with write access to it"
|
|
993
1062
|
) from error
|
|
994
|
-
if (details or {}).get("permissions", {}).get("push") is False:
|
|
1063
|
+
if pushes and (details or {}).get("permissions", {}).get("push") is False:
|
|
995
1064
|
raise ValueError(
|
|
996
1065
|
f"GitHub access from {access['source']} cannot push to {repo}; use an account or token with write access to it"
|
|
997
1066
|
)
|
|
@@ -1072,21 +1141,43 @@ def submit(root, repo, workflow, parsed, path):
|
|
|
1072
1141
|
) from error
|
|
1073
1142
|
record["state"] = "dispatch_unknown"
|
|
1074
1143
|
save(path, record)
|
|
1075
|
-
|
|
1076
|
-
if DECLARED_INPUTS.search(
|
|
1144
|
+
declared = declared_inputs(
|
|
1077
1145
|
within(root, f"{WORKFLOW_DIRECTORY}/{workflow}").read_text()
|
|
1078
|
-
)
|
|
1079
|
-
|
|
1146
|
+
)
|
|
1147
|
+
return dispatch(record, {"execution_id": execution_id, "request": "-"}, declared)
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
def declared_inputs(text):
|
|
1151
|
+
return {
|
|
1152
|
+
name
|
|
1153
|
+
for name in DISPATCH_INPUTS
|
|
1154
|
+
if re.search(rf"^[ \t]*\"?{name}\"?[ \t]*:", text, re.MULTILINE)
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
def dispatch(record, inputs, declared, *, run_details=False):
|
|
1159
|
+
payload = {"ref": record["branch"]}
|
|
1160
|
+
sent = {name: value for name, value in inputs.items() if name in declared}
|
|
1161
|
+
if sent:
|
|
1162
|
+
payload["inputs"] = sent
|
|
1163
|
+
path = f"actions/workflows/{record['workflow']}/dispatches"
|
|
1080
1164
|
try:
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1165
|
+
try:
|
|
1166
|
+
response = api(
|
|
1167
|
+
record["repository"],
|
|
1168
|
+
path,
|
|
1169
|
+
method="POST",
|
|
1170
|
+
payload={**payload, "return_run_details": True}
|
|
1171
|
+
if run_details
|
|
1172
|
+
else payload,
|
|
1173
|
+
)
|
|
1174
|
+
except RuntimeError as error:
|
|
1175
|
+
if not run_details or getattr(error, "http_status", None) != 422:
|
|
1176
|
+
raise
|
|
1177
|
+
response = api(record["repository"], path, method="POST", payload=payload)
|
|
1087
1178
|
except RuntimeError as error:
|
|
1088
1179
|
raise ValueError(
|
|
1089
|
-
f"Dispatching {workflow} on {record['branch']} failed{http_status(error)}; the GitHub account needs write access to Actions, and the registered workflow must accept workflow_dispatch. Check the Actions tab, then resume with --cloud-watch {
|
|
1180
|
+
f"Dispatching {record['workflow']} on {record['branch']} failed{http_status(error)}; the GitHub account needs write access to Actions, and the registered workflow must accept workflow_dispatch. Check the Actions tab, then resume with --cloud-watch {record['id']}"
|
|
1090
1181
|
) from error
|
|
1091
1182
|
if isinstance(response, dict) and response.get("workflow_run_id"):
|
|
1092
1183
|
record["runId"] = response["workflow_run_id"]
|
|
@@ -1095,6 +1186,84 @@ def submit(root, repo, workflow, parsed, path):
|
|
|
1095
1186
|
return record
|
|
1096
1187
|
|
|
1097
1188
|
|
|
1189
|
+
def branch_head(repo, branch):
|
|
1190
|
+
try:
|
|
1191
|
+
found = api(repo, f"git/ref/heads/{branch}")
|
|
1192
|
+
except RuntimeError as error:
|
|
1193
|
+
if getattr(error, "http_status", None) == 404:
|
|
1194
|
+
raise ValueError(f"{repo} has no branch {branch}") from error
|
|
1195
|
+
raise
|
|
1196
|
+
sha = ((found or {}).get("object") or {}).get("sha", "")
|
|
1197
|
+
if not SHA.fullmatch(sha):
|
|
1198
|
+
raise ValueError(f"GitHub gave no commit for branch {branch}")
|
|
1199
|
+
return sha
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
def branch_workflow(repo, workflow, sha):
|
|
1203
|
+
path = f"{WORKFLOW_DIRECTORY}/{workflow}"
|
|
1204
|
+
try:
|
|
1205
|
+
found = api(repo, f"contents/{path}?ref={sha}")
|
|
1206
|
+
except RuntimeError as error:
|
|
1207
|
+
raise ValueError(
|
|
1208
|
+
f"Could not read {path} at {sha[:12]}{http_status(error)}; the branch needs the workflow"
|
|
1209
|
+
) from error
|
|
1210
|
+
return base64.b64decode((found or {}).get("content", "")).decode(errors="replace")
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
def submit_as_is(root, repo, workflow, parsed, path):
|
|
1214
|
+
execution_id = parsed["id"]
|
|
1215
|
+
branch = parsed["cloud"]["--cloud-ref"]
|
|
1216
|
+
request, _ = replay_request(root, parsed)
|
|
1217
|
+
sha = branch_head(repo, branch)
|
|
1218
|
+
if "--cloud-preview" in parsed["switches"]:
|
|
1219
|
+
return {
|
|
1220
|
+
"preview": True,
|
|
1221
|
+
"repository": repo,
|
|
1222
|
+
"branch": branch,
|
|
1223
|
+
"sha": sha,
|
|
1224
|
+
"request": request,
|
|
1225
|
+
}
|
|
1226
|
+
if path.exists():
|
|
1227
|
+
record = json.loads(path.read_text())
|
|
1228
|
+
if (
|
|
1229
|
+
record["request"] != request
|
|
1230
|
+
or record["repository"].lower() != repo.lower()
|
|
1231
|
+
or record["branch"] != branch
|
|
1232
|
+
):
|
|
1233
|
+
raise ValueError("Execution ID already belongs to a different request")
|
|
1234
|
+
status(record, fetch_result=False)
|
|
1235
|
+
return record
|
|
1236
|
+
preflight(repo, workflow, pushes=False)
|
|
1237
|
+
declared = declared_inputs(branch_workflow(repo, workflow, sha))
|
|
1238
|
+
if "request" not in declared:
|
|
1239
|
+
raise ValueError(
|
|
1240
|
+
f"{WORKFLOW_DIRECTORY}/{workflow} on {branch} declares no request input, so it cannot take a replay; add request (type: string, required: false) under on.workflow_dispatch.inputs and land it on {branch}"
|
|
1241
|
+
)
|
|
1242
|
+
record = {
|
|
1243
|
+
"id": execution_id,
|
|
1244
|
+
"repository": repo,
|
|
1245
|
+
"workflow": workflow,
|
|
1246
|
+
"branch": branch,
|
|
1247
|
+
"snapshot": False,
|
|
1248
|
+
"request": request,
|
|
1249
|
+
"state": "dispatch_unknown",
|
|
1250
|
+
"sha": sha,
|
|
1251
|
+
"dispatchedAt": time.strftime(
|
|
1252
|
+
"%Y-%m-%dT%H:%M:%SZ", time.gmtime(time.time() - 5)
|
|
1253
|
+
),
|
|
1254
|
+
}
|
|
1255
|
+
save(path, record)
|
|
1256
|
+
progress(
|
|
1257
|
+
f"Replaying {branch} at {sha[:12]} as it is on GitHub, without local changes. Execution {execution_id}; resume with --cloud-watch {execution_id}"
|
|
1258
|
+
)
|
|
1259
|
+
return dispatch(
|
|
1260
|
+
record,
|
|
1261
|
+
{"execution_id": execution_id, "request": json.dumps(request)},
|
|
1262
|
+
declared,
|
|
1263
|
+
run_details=True,
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
|
|
1098
1267
|
def report_steps(record, shown):
|
|
1099
1268
|
job = replay_job(record)
|
|
1100
1269
|
for step in (job or {}).get("steps", []):
|
|
@@ -1140,7 +1309,8 @@ def run_cli(argv):
|
|
|
1140
1309
|
path = directory / f"{execution_id}.json"
|
|
1141
1310
|
with execution_lock(directory, execution_id):
|
|
1142
1311
|
if operation == "submit":
|
|
1143
|
-
|
|
1312
|
+
submitter = submit_as_is if "--cloud-ref" in parsed["cloud"] else submit
|
|
1313
|
+
record = submitter(root, repo, workflow, parsed, path)
|
|
1144
1314
|
if record.get("preview"):
|
|
1145
1315
|
return record, None
|
|
1146
1316
|
else:
|
|
@@ -1216,14 +1386,86 @@ def snapshot_request(root, commit):
|
|
|
1216
1386
|
return request
|
|
1217
1387
|
|
|
1218
1388
|
|
|
1389
|
+
def dispatched_inputs():
|
|
1390
|
+
path = os.environ.get("GITHUB_EVENT_PATH")
|
|
1391
|
+
if not path or not Path(path).is_file():
|
|
1392
|
+
return {}
|
|
1393
|
+
inputs = json.loads(Path(path).read_text()).get("inputs")
|
|
1394
|
+
return inputs if isinstance(inputs, dict) else {}
|
|
1395
|
+
|
|
1396
|
+
|
|
1397
|
+
def decode_request(text):
|
|
1398
|
+
try:
|
|
1399
|
+
return json.loads(text)
|
|
1400
|
+
except ValueError:
|
|
1401
|
+
pass
|
|
1402
|
+
try:
|
|
1403
|
+
return json.loads(base64.b64decode(text, validate=True))
|
|
1404
|
+
except ValueError as error:
|
|
1405
|
+
raise ValueError(
|
|
1406
|
+
"The request input is neither JSON nor base64-encoded JSON"
|
|
1407
|
+
) from error
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
def dispatched_request():
|
|
1411
|
+
branch = os.environ.get("GITHUB_REF_NAME", "")
|
|
1412
|
+
inputs = dispatched_inputs()
|
|
1413
|
+
text = str(inputs.get("request") or "").strip()
|
|
1414
|
+
if text in ("", "-"):
|
|
1415
|
+
raise ValueError(
|
|
1416
|
+
f"A replay of {branch} takes its request from the workflow_dispatch request input, and this run has none; start replays with bitfab-replay --cloud, or --cloud --cloud-ref {branch} to replay the branch as it is"
|
|
1417
|
+
)
|
|
1418
|
+
request = decode_request(text)
|
|
1419
|
+
if not isinstance(request, dict):
|
|
1420
|
+
raise ValueError("The request input must be a JSON object")
|
|
1421
|
+
version = request.get("version", REQUEST_VERSION)
|
|
1422
|
+
if not isinstance(version, int) or version < OLDEST_DISPATCHED_VERSION:
|
|
1423
|
+
raise ValueError(
|
|
1424
|
+
f"Request version {version!r} is not supported; send version {REQUEST_VERSION}, or leave version out"
|
|
1425
|
+
)
|
|
1426
|
+
if version > REQUEST_VERSION:
|
|
1427
|
+
raise ValueError(
|
|
1428
|
+
f"This replay was submitted by a newer SDK than the one {branch} installs; the request is version {version} and this SDK reads up to {REQUEST_VERSION}"
|
|
1429
|
+
)
|
|
1430
|
+
execution_id = request.get("id") or inputs.get("execution_id") or str(uuid.uuid4())
|
|
1431
|
+
if not isinstance(execution_id, str) or not UUID.fullmatch(execution_id):
|
|
1432
|
+
raise ValueError("The request id must be a UUID")
|
|
1433
|
+
if inputs.get("execution_id") and inputs["execution_id"] != execution_id:
|
|
1434
|
+
raise ValueError("The request id does not match the execution_id input")
|
|
1435
|
+
args = request.get("args")
|
|
1436
|
+
validate_arguments(args)
|
|
1437
|
+
if request.get("check") is True and "--dry-run" not in args:
|
|
1438
|
+
args = [*args, "--dry-run"]
|
|
1439
|
+
normalized = {
|
|
1440
|
+
"version": REQUEST_VERSION,
|
|
1441
|
+
"id": execution_id,
|
|
1442
|
+
"cwd": request.get("cwd") or ".",
|
|
1443
|
+
"args": args,
|
|
1444
|
+
}
|
|
1445
|
+
timeout = request.get("timeoutMinutes")
|
|
1446
|
+
if timeout is not None:
|
|
1447
|
+
if not isinstance(timeout, int) or not 1 <= timeout <= 7200:
|
|
1448
|
+
raise ValueError("timeoutMinutes must be 1..7200")
|
|
1449
|
+
normalized["timeoutMinutes"] = timeout
|
|
1450
|
+
if request.get("fullOutput") is True:
|
|
1451
|
+
normalized["fullOutput"] = True
|
|
1452
|
+
return normalized
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
def runner_request(root, commit):
|
|
1456
|
+
if os.environ.get("GITHUB_REF_NAME", "").startswith(PREFIX):
|
|
1457
|
+
return snapshot_request(root, commit)
|
|
1458
|
+
return dispatched_request()
|
|
1459
|
+
|
|
1460
|
+
|
|
1219
1461
|
def execute():
|
|
1220
1462
|
if os.environ.get("GITHUB_RUN_ATTEMPT") != "1":
|
|
1221
1463
|
raise ValueError("Submit a new replay instead of rerunning an Actions job")
|
|
1222
1464
|
root = root_directory()
|
|
1223
1465
|
commit = os.environ["GITHUB_SHA"]
|
|
1224
1466
|
if git(root, "rev-parse", "HEAD") != commit:
|
|
1225
|
-
raise ValueError("Runner checkout does not match the dispatched
|
|
1226
|
-
request =
|
|
1467
|
+
raise ValueError("Runner checkout does not match the dispatched commit")
|
|
1468
|
+
request = runner_request(root, commit)
|
|
1227
1469
|
directory = within(root, request["cwd"])
|
|
1228
1470
|
check_secrets(root)
|
|
1229
1471
|
names = {option_name(value) for value in request["args"]}
|
|
@@ -1488,12 +1730,18 @@ def annotate(result):
|
|
|
1488
1730
|
if result.get("stoppedEarly"):
|
|
1489
1731
|
message = f"Replay stopped early: {result['stoppedEarly']}"
|
|
1490
1732
|
elif result["exitCode"]:
|
|
1491
|
-
message = f"Replay exited {result['exitCode']}
|
|
1733
|
+
message = f"Replay exited {result['exitCode']}, so this job exits with it too"
|
|
1492
1734
|
else:
|
|
1493
1735
|
return
|
|
1494
1736
|
print(f"::warning title=Bitfab replay::{message}", flush=True)
|
|
1495
1737
|
|
|
1496
1738
|
|
|
1739
|
+
def runner_exit_code(result):
|
|
1740
|
+
if result.get("stoppedEarly"):
|
|
1741
|
+
return result["exitCode"] or 1
|
|
1742
|
+
return result["exitCode"]
|
|
1743
|
+
|
|
1744
|
+
|
|
1497
1745
|
def summary_markdown(result):
|
|
1498
1746
|
summary = result_summary(result["stdout"]) or {}
|
|
1499
1747
|
counts = summary.get("counts", {})
|
|
@@ -1828,7 +2076,11 @@ def workflow_document(steps, run, directory, env, *, runs_on, environment):
|
|
|
1828
2076
|
]
|
|
1829
2077
|
return {
|
|
1830
2078
|
"name": "Bitfab replay",
|
|
1831
|
-
"on":
|
|
2079
|
+
"on": {
|
|
2080
|
+
"workflow_dispatch": {
|
|
2081
|
+
"inputs": {"request": {"type": "string", "required": False}}
|
|
2082
|
+
}
|
|
2083
|
+
},
|
|
1832
2084
|
"permissions": {"contents": "read"},
|
|
1833
2085
|
"jobs": {"replay": job},
|
|
1834
2086
|
}
|
|
@@ -1957,8 +2209,9 @@ def main():
|
|
|
1957
2209
|
print_json(configure_secrets(argv[1:]))
|
|
1958
2210
|
return 0
|
|
1959
2211
|
if argv == ["--cloud-execute"]:
|
|
1960
|
-
|
|
1961
|
-
|
|
2212
|
+
result = execute()
|
|
2213
|
+
annotate(result)
|
|
2214
|
+
return runner_exit_code(result)
|
|
1962
2215
|
if "-h" in argv or "--help" in argv:
|
|
1963
2216
|
print(HELP, end="")
|
|
1964
2217
|
return 0
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
serializeValue,
|
|
18
18
|
toJsonSafe,
|
|
19
19
|
toJsonSafeReport
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-B363XZTL.js";
|
|
21
21
|
import {
|
|
22
22
|
BitfabError,
|
|
23
23
|
CONTENT_OFF_KEY,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
readEnv,
|
|
37
37
|
recordCallerTraceMetadata,
|
|
38
38
|
retireTraceMetadata
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-ROZEHSGO.js";
|
|
40
40
|
import {
|
|
41
41
|
__privateAdd,
|
|
42
42
|
__privateGet,
|
|
@@ -6253,7 +6253,7 @@ var Bitfab = class {
|
|
|
6253
6253
|
await runWithSeedContext({ traceId }, async () => target(...args));
|
|
6254
6254
|
} finally {
|
|
6255
6255
|
try {
|
|
6256
|
-
const { flushTraces: flushTraces2 } = await import("./http-
|
|
6256
|
+
const { flushTraces: flushTraces2 } = await import("./http-2UZ6CM3K.js");
|
|
6257
6257
|
await flushTraces2(3e4);
|
|
6258
6258
|
} finally {
|
|
6259
6259
|
unrecorded = activeTraceStates.delete(traceId);
|
|
@@ -6312,7 +6312,7 @@ var Bitfab = class {
|
|
|
6312
6312
|
`Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
|
|
6313
6313
|
);
|
|
6314
6314
|
}
|
|
6315
|
-
const { replay: doReplay } = await import("./replay-
|
|
6315
|
+
const { replay: doReplay } = await import("./replay-KQCLQRJT.js");
|
|
6316
6316
|
return doReplay(
|
|
6317
6317
|
this.httpClient,
|
|
6318
6318
|
this.serviceUrl,
|
|
@@ -6658,7 +6658,7 @@ async function seedFromRegistry(registry, pipeline, cases, options = {}) {
|
|
|
6658
6658
|
sessionId: seedCase.sessionId
|
|
6659
6659
|
})
|
|
6660
6660
|
);
|
|
6661
|
-
const { flushTraces: flushTraces2 } = await import("./http-
|
|
6661
|
+
const { flushTraces: flushTraces2 } = await import("./http-2UZ6CM3K.js");
|
|
6662
6662
|
await flushTraces2(3e4);
|
|
6663
6663
|
return { pipeline, traceFunctionKey, traceIds };
|
|
6664
6664
|
}
|
|
@@ -6703,4 +6703,4 @@ export {
|
|
|
6703
6703
|
reseedFromRegistry,
|
|
6704
6704
|
seedFromRegistry
|
|
6705
6705
|
};
|
|
6706
|
-
//# sourceMappingURL=chunk-
|
|
6706
|
+
//# sourceMappingURL=chunk-6NUSTEQ6.js.map
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
replayContextReady,
|
|
7
7
|
runWithReplayContext,
|
|
8
8
|
unrefTimer
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ROZEHSGO.js";
|
|
10
10
|
import {
|
|
11
11
|
runWithCaptureSuppressed,
|
|
12
12
|
warnOnce,
|
|
@@ -1884,4 +1884,4 @@ export {
|
|
|
1884
1884
|
warnJudgeAssertionsDeprecated,
|
|
1885
1885
|
replay
|
|
1886
1886
|
};
|
|
1887
|
-
//# sourceMappingURL=chunk-
|
|
1887
|
+
//# sourceMappingURL=chunk-B363XZTL.js.map
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
BitfabError,
|
|
3
3
|
warnOnce,
|
|
4
4
|
writeOnce
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MS3GWCJB.js";
|
|
6
6
|
|
|
7
7
|
// src/codeChange.ts
|
|
8
8
|
var NUL = String.fromCharCode(0);
|
|
@@ -316,7 +316,7 @@ async function seedFromRegistry(registry, pipeline, cases, options = {}) {
|
|
|
316
316
|
sessionId: seedCase.sessionId
|
|
317
317
|
})
|
|
318
318
|
);
|
|
319
|
-
const { flushTraces: flushTraces2 } = await import("./http-
|
|
319
|
+
const { flushTraces: flushTraces2 } = await import("./http-BD3WK3FL.js");
|
|
320
320
|
await flushTraces2(3e4);
|
|
321
321
|
return { pipeline, traceFunctionKey, traceIds };
|
|
322
322
|
}
|
|
@@ -1007,4 +1007,4 @@ export {
|
|
|
1007
1007
|
runRegistryMain,
|
|
1008
1008
|
runIfEntrypoint
|
|
1009
1009
|
};
|
|
1010
|
-
//# sourceMappingURL=chunk-
|
|
1010
|
+
//# sourceMappingURL=chunk-J342BFLH.js.map
|
|
@@ -181,7 +181,7 @@ function encodeRequestBody(body) {
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// src/version.generated.ts
|
|
184
|
-
var __version__ = "0.64.
|
|
184
|
+
var __version__ = "0.64.6";
|
|
185
185
|
var __packageName__ = "bitfab";
|
|
186
186
|
|
|
187
187
|
// src/errors.ts
|
|
@@ -2500,4 +2500,4 @@ export {
|
|
|
2500
2500
|
HttpClient,
|
|
2501
2501
|
experimentIdWireFields
|
|
2502
2502
|
};
|
|
2503
|
-
//# sourceMappingURL=chunk-
|
|
2503
|
+
//# sourceMappingURL=chunk-MS3GWCJB.js.map
|