16 Commits
Author SHA1 Message Date
Philipp e95a834903 chore: migrate package manager from npm to pnpm
Lint / pre-commit Linting (push) Successful in 2m37s
Test / Run Tests (push) Failing after 1m6s
2026-08-10 23:50:17 +02:00
Renovate 76a263859b chore(deps): update actions/setup-node action to v7 (#108)
Lint / pre-commit Linting (push) Successful in 1m23s
Test / Run Tests (push) Successful in 1m53s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-07-25 01:50:45 +02:00
Renovate 2f32772d0a chore(deps): update actions/setup-node action to v6.5.0 (#107)
Lint / pre-commit Linting (push) Successful in 1m2s
Test / Run Tests (push) Successful in 4m48s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-07-25 01:06:02 +02:00
Renovate 7a447adf45 chore(deps): update actions/setup-python action to v7 (#109)
Lint / pre-commit Linting (push) Successful in 1m0s
Test / Run Tests (push) Successful in 1m35s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-07-25 01:04:51 +02:00
Renovate dcdca8e753 chore(deps): update actions/checkout action to v7.0.1 (#106)
Lint / pre-commit Linting (push) Successful in 58s
Test / Run Tests (push) Successful in 1m36s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-07-25 00:32:05 +02:00
Renovate 9dcad628d4 chore(deps): update pre-commit hook python-jsonschema/check-jsonschema to v0.37.4 (#105)
Lint / pre-commit Linting (push) Successful in 57s
Test / Run Tests (push) Successful in 1m27s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-07-03 20:53:34 +02:00
Renovate c9b5d215f6 chore(deps): update actions/setup-python action to v6.3.0 (#104)
Lint / pre-commit Linting (push) Successful in 58s
Test / Run Tests (push) Successful in 1m36s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-06-29 02:54:05 +02:00
Renovate e8534d7fae chore(deps): update actions/checkout action to v7 (#103)
Lint / pre-commit Linting (push) Successful in 1m9s
Test / Run Tests (push) Successful in 1m32s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-06-22 02:36:03 +02:00
Renovate 873de41ead chore(deps): update pre-commit hook python-jsonschema/check-jsonschema to v0.37.3 (#100)
Lint / pre-commit Linting (push) Successful in 1m13s
Test / Run Tests (push) Successful in 1m52s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-06-19 03:14:22 +02:00
Renovate 36e34e99c5 chore(deps): update actions/checkout action to v6.0.3 (#98)
Lint / pre-commit Linting (push) Successful in 1m3s
Test / Run Tests (push) Successful in 1m26s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-06-05 16:45:22 +02:00
Renovate 2292b914ad chore(deps): update vitest monorepo to v4.1.8 (#97)
Lint / pre-commit Linting (push) Successful in 54s
Test / Run Tests (push) Successful in 1m21s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@vitest/coverage-v8](https://vitest.dev/guide/coverage) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8)) | [`4.1.7` → `4.1.8`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.1.7/4.1.8) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.1.8?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.1.7/4.1.8?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.1.7` → `4.1.8`](https://renovatebot.com/diffs/npm/vitest/4.1.7/4.1.8) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.8?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.1.7/4.1.8?slim=true) |

---

### Release Notes

<details>
<summary>vitest-dev/vitest (@&#8203;vitest/coverage-v8)</summary>

### [`v4.1.8`](https://github.com/vitest-dev/vitest/releases/tag/v4.1.8)

[Compare Source](https://github.com/vitest-dev/vitest/compare/v4.1.7...v4.1.8)

#####    🐞 Bug Fixes

- **browser**:
  - Disable client `cdp` API when `allowWrite/allowExec: false` \[backport to v4]  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Codex** in [#&#8203;10450](https://github.com/vitest-dev/vitest/issues/10450) [<samp>(e4067)</samp>](https://github.com/vitest-dev/vitest/commit/e4067b3b1)
  - Remove orphaned Playwright route when same module is mocked via multiple ids \[backport to v4]  -  by [@&#8203;toxik](https://github.com/toxik) and [@&#8203;Zelys-DFKH](https://github.com/Zelys-DFKH) in [#&#8203;10474](https://github.com/vitest-dev/vitest/issues/10474) [<samp>(675b4)</samp>](https://github.com/vitest-dev/vitest/commit/675b4343f)

#####     [View changes on GitHub](https://github.com/vitest-dev/vitest/compare/v4.1.7...v4.1.8)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "on monday,on friday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yMDMuMCIsInVwZGF0ZWRJblZlciI6IjQzLjIwMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0aW9uIiwibnBtIl19-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/97
Reviewed-by: LLMReview <27+autoreview@noreplay.horstenkamp.eu>
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-06-05 13:46:00 +02:00
Renovate bdd0fbfd7e chore(deps): update vitest monorepo to v4.1.7 (#96)
Lint / pre-commit Linting (push) Successful in 51s
Test / Run Tests (push) Successful in 1m21s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@vitest/coverage-v8](https://vitest.dev/guide/coverage) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8)) | [`4.1.6` → `4.1.7`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.1.6/4.1.7) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.1.7?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.1.6/4.1.7?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.1.6` → `4.1.7`](https://renovatebot.com/diffs/npm/vitest/4.1.6/4.1.7) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.7?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.1.6/4.1.7?slim=true) |

---

### Release Notes

<details>
<summary>vitest-dev/vitest (@&#8203;vitest/coverage-v8)</summary>

### [`v4.1.7`](https://github.com/vitest-dev/vitest/releases/tag/v4.1.7)

[Compare Source](https://github.com/vitest-dev/vitest/compare/v4.1.6...v4.1.7)

#####    🐞 Bug Fixes

- **runner**: Limit concurrency per task branch in addition to per leaf callbacks (backport)  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;10384](https://github.com/vitest-dev/vitest/issues/10384) [<samp>(4f0f2)</samp>](https://github.com/vitest-dev/vitest/commit/4f0f2a1ee)

#####     [View changes on GitHub](https://github.com/vitest-dev/vitest/compare/v4.1.6...v4.1.7)

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - "on monday,on friday"
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these updates again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xODUuMSIsInVwZGF0ZWRJblZlciI6IjQzLjE4NS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0aW9uIiwibnBtIl19-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/96
Reviewed-by: LLMReview <27+autoreview@noreplay.horstenkamp.eu>
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-05-25 18:38:53 +02:00
Renovate df5d81df5c chore(deps): update actions/setup-python action to v6.2.0 (#95)
Lint / pre-commit Linting (push) Successful in 1m2s
Test / Run Tests (push) Successful in 1m17s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-05-23 01:04:04 +02:00
Renovate 0b930f5645 chore(deps): update actions/setup-node action to v6.4.0 (#94)
Lint / pre-commit Linting (push) Successful in 1m1s
Test / Run Tests (push) Successful in 1m23s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-05-23 00:57:45 +02:00
Renovate 67f3f15c2b chore(deps): update actions/checkout action to v6.0.2 (#93)
Lint / pre-commit Linting (push) Successful in 58s
Test / Run Tests (push) Successful in 1m17s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-05-23 00:47:20 +02:00
Philipp d0a08da728 feat: add rollback_on_failure feature (#88)
Lint / pre-commit Linting (push) Successful in 44s
Test / Run Tests (push) Successful in 1m2s
This PR adds the ability to download existing code before deployment and automatically roll back if the post-deployment monitor detects a failure.

Reviewed-on: #88
Reviewed-by: LLMReview <27+autoreview@noreplay.horstenkamp.eu>
2026-05-17 13:13:58 +02:00
8 changed files with 167 additions and 53 deletions

No files matched your search

+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
name: pre-commit Linting
runs-on: pi
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
- run: pip install pre-commit
shell: bash
- name: Pre Commit
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
name: Run Tests
runs-on: pi
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
- uses: pnpm/action-setup@v4
+2 -1
View File
@@ -21,6 +21,7 @@ repos:
hooks:
- id: pretty-format-yaml
args: [--autofix]
exclude: ^pnpm-lock\.yaml$
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
@@ -29,7 +30,7 @@ repos:
types_or: [css, javascript]
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.37.2
rev: 0.37.4
hooks:
- id: check-renovate
- id: check-github-actions
+1
View File
@@ -6,6 +6,7 @@ This repository is maintained by Gemini.
* **Test-Driven Development (TDD):** Wherever possible, Test-Driven Development principles should be followed. Write tests before writing the code they are intended to validate.
* **Pre-commit Hooks:** Ensure that `pre-commit` hooks are installed and active before making any commits. This can be done by running `pre-commit install` in your local repository.
* **Note for Gemini:** Git commits trigger pre-commit hooks, which can take several seconds (or minutes) to complete. Checking the command status for git commit is only appropriate every 120s.
## Repository Comparison
+73 -22
View File
@@ -20,6 +20,23 @@ vi.mock("../monitor.js", () => ({
}),
}));
// Mock screeps-api
vi.mock("screeps-api", () => {
const mockApi = {
auth: vi.fn().mockResolvedValue(),
code: {
get: vi.fn().mockResolvedValue({ ok: 1, modules: { main: "old_code" } }),
set: vi.fn().mockResolvedValue({ ok: 1 }),
},
};
// Use a regular function so it can be called with `new`
return {
ScreepsAPI: vi.fn(function () {
return mockApi;
}),
};
});
import * as core from "@actions/core";
import { monitorConsole } from "../monitor.js";
@@ -29,7 +46,9 @@ import {
readReplaceAndWriteFiles,
readFilesIntoDict,
applyOnAction,
postCode,
} from "../index.js";
import { ScreepsAPI } from "screeps-api";
import fs from "fs";
import path from "path";
import os from "os";
@@ -231,31 +250,31 @@ describe("glob functionality", () => {
describe("applyOnAction", () => {
beforeEach(() => vi.clearAllMocks());
it("'ignore' + true → no core call", () => {
applyOnAction("ignore", true, "msg");
it("'ignore' + true → no core call, returns false", () => {
expect(applyOnAction("ignore", true, "msg")).toBe(false);
expect(core.warning).not.toHaveBeenCalled();
expect(core.setFailed).not.toHaveBeenCalled();
});
it("'warn' + true → core.warning() called with message", () => {
applyOnAction("warn", true, "boom");
it("'warn' + true → core.warning() called with message, returns false", () => {
expect(applyOnAction("warn", true, "boom")).toBe(false);
expect(core.warning).toHaveBeenCalledWith("boom");
expect(core.setFailed).not.toHaveBeenCalled();
});
it("'fail' + true → core.setFailed() called with message", () => {
applyOnAction("fail", true, "boom");
it("'fail' + true → core.setFailed() called with message, returns true", () => {
expect(applyOnAction("fail", true, "boom")).toBe(true);
expect(core.setFailed).toHaveBeenCalledWith("boom");
expect(core.warning).not.toHaveBeenCalled();
});
it("'fail' + false → no core call", () => {
applyOnAction("fail", false, "boom");
it("'fail' + false → no core call, returns false", () => {
expect(applyOnAction("fail", false, "boom")).toBe(false);
expect(core.setFailed).not.toHaveBeenCalled();
});
it("'warn' + false → no core call", () => {
applyOnAction("warn", false, "msg");
it("'warn' + false → no core call, returns false", () => {
expect(applyOnAction("warn", false, "msg")).toBe(false);
expect(core.warning).not.toHaveBeenCalled();
});
});
@@ -270,28 +289,60 @@ describe("postCode — monitor wiring", () => {
// Default core mocks
core.getInput.mockImplementation((name) => {
if (name === "monitor") return "0";
if (name === "token") return "test-token";
if (name === "branch") return "default";
if (name === "on_traceback") return "fail";
return "";
});
core.getBooleanInput.mockReturnValue(false);
core.getBooleanInput.mockImplementation((name) => {
if (name === "rollback_on_failure") return false;
return false;
});
});
it("does not call monitorConsole when monitor=0 (default)", async () => {
// We need to mock the rest of postCode to not fail before it hits the monitor block
// This is a bit complex as postCode is large, but we can mock the inputs to exit early or mock the API
// Actually, I'll just check if monitorConsole is called.
// We just run postCode with monitor=0 and verify monitorConsole is not called.
await postCode();
expect(monitorConsole).not.toHaveBeenCalled();
});
// For this test, I'll make validateAuthentication fail so it returns early but after input check
it("rolls back to previous code when monitor detects a failure and rollback_on_failure is true", async () => {
// Setup inputs for monitor and rollback
core.getInput.mockImplementation((name) => {
if (name === "monitor") return "0";
if (name === "monitor") return "10";
if (name === "token") return "test-token";
if (name === "branch") return "default";
if (name === "on_traceback") return "fail";
return "";
});
core.getBooleanInput.mockImplementation((name) => {
if (name === "rollback_on_failure") return true;
return false;
});
// We'll just run a partial check or rely on the monitor unit tests for depth
// The wiring in index.js is:
// const monitorTicks = parseInt(core.getInput("monitor") || "0", 10);
// if (monitorTicks > 0) { ... }
// Simulate a failure in monitorConsole
monitorConsole.mockResolvedValueOnce({
sawTraceback: true, // Should trigger "fail" due to on_traceback=fail
sawErrorLog: false,
sawWarningLog: false,
});
// Testing the logic inside index.js directly by calling postCode would require full environment mock.
// I'll stick to the applyOnAction unit tests and rely on monitor.test.js for the heavy lifting.
await postCode();
// Verify rollback was performed
const mockApiInstance = new ScreepsAPI();
// `code.set` should be called twice:
// 1st time: uploading the new files
// 2nd time: rolling back to oldCode
expect(mockApiInstance.code.set).toHaveBeenCalledTimes(2);
expect(mockApiInstance.code.set).toHaveBeenNthCalledWith(2, "default", {
main: "old_code",
});
// Verify it called core.setFailed due to traceback
expect(core.setFailed).toHaveBeenCalledWith(
"Screeps console: traceback detected",
);
});
});
+4
View File
@@ -67,6 +67,10 @@ inputs:
description: 'Print a progress update every N ticks when log_to_file=true (default: 10).'
required: false
default: '10'
rollback_on_failure:
description: 'Automatically rollback to previous code if the monitor detects failures. Requires monitor > 0. (default: false)'
required: false
default: 'false'
outputs:
saw_traceback:
description: true if a JS traceback was detected during monitoring.
+1 -1
View File
@@ -24,4 +24,4 @@ const E=g(9896);const p=g(857);const C=g(6928);const f=g(6982);const Q={fs:E.con
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */;const escape=A=>A.replace(/\n/g,"%0A").replace(/\r/g,"%0D").replace(/"/g,"%22");const normalizeLinefeeds=A=>A.replace(/\r?\n|\r/g,"\r\n");const E=[];const p=new Uint8Array([13,10]);b=0;let C=false;for(const[i,f]of A){if(typeof f==="string"){const A=P.encode(g+`; name="${escape(normalizeLinefeeds(i))}"`+`\r\n\r\n${normalizeLinefeeds(f)}\r\n`);E.push(A);b+=A.byteLength}else{const A=P.encode(`${g}; name="${escape(normalizeLinefeeds(i))}"`+(f.name?`; filename="${escape(f.name)}"`:"")+"\r\n"+`Content-Type: ${f.type||"application/octet-stream"}\r\n\r\n`);E.push(A,f,p);if(typeof f.size==="number"){b+=A.byteLength+f.size+p.byteLength}else{C=true}}}const f=P.encode(`--${i}--`);E.push(f);b+=f.byteLength;if(C){b=null}y=A;I=async function*(){for(const A of E){if(A.stream){yield*A.stream()}else{yield A}}};D="multipart/form-data; boundary="+i}else if(f(A)){y=A;b=A.size;if(A.type){D=A.type}}else if(typeof A[Symbol.asyncIterator]==="function"){if(i){throw new TypeError("keepalive")}if(p.isDisturbed(A)||A.locked){throw new TypeError("Response body object should not be disturbed or locked")}E=A instanceof _?A:C(A)}if(typeof y==="string"||p.isBuffer(y)){b=Buffer.byteLength(y)}if(I!=null){let i;E=new _({async start(){i=I(A)[Symbol.asyncIterator]()},async pull(A){const{value:g,done:p}=await i.next();if(p){queueMicrotask((()=>{A.close()}))}else{if(!L(E)){A.enqueue(new Uint8Array(g))}}return A.desiredSize>0},async cancel(A){await i.return()},type:undefined})}const R={stream:E,source:y,length:b};return[R,D]}function safelyExtractBody(A,i=false){if(!_){_=g(3774).ReadableStream}if(A instanceof _){U(!p.isDisturbed(A),"The body has already been consumed.");U(!A.locked,"The stream is locked.")}return extractBody(A,i)}function cloneBody(A){const[i,g]=A.stream.tee();const E=S(g,{transfer:[g]});const[,p]=E.tee();A.stream=i;return{stream:p,length:A.length,source:A.source}}async function*consumeBody(A){if(A){if(M(A)){yield A}else{const i=A.stream;if(p.isDisturbed(i)){throw new TypeError("The body has already been consumed.")}if(i.locked){throw new TypeError("The stream is locked.")}i[x]=true;yield*i}}}function throwIfAborted(A){if(A.aborted){throw new k("The operation was aborted.","AbortError")}}function bodyMixinMethods(A){const i={blob(){return specConsumeBody(this,(A=>{let i=bodyMimeType(this);if(i==="failure"){i=""}else if(i){i=Y(i)}return new v([A],{type:i})}),A)},arrayBuffer(){return specConsumeBody(this,(A=>new Uint8Array(A).buffer),A)},text(){return specConsumeBody(this,utf8DecodeBytes,A)},json(){return specConsumeBody(this,parseJSONFromBytes,A)},async formData(){R.brandCheck(this,A);throwIfAborted(this[D]);const i=this.headers.get("Content-Type");if(/multipart\/form-data/.test(i)){const A={};for(const[i,g]of this.headers)A[i.toLowerCase()]=g;const i=new b;let g;try{g=new E({headers:A,preservePath:true})}catch(A){throw new k(`${A}`,"AbortError")}g.on("field",((A,g)=>{i.append(A,g)}));g.on("file",((A,g,E,p,C)=>{const f=[];if(p==="base64"||p.toLowerCase()==="base64"){let p="";g.on("data",(A=>{p+=A.toString().replace(/[\r\n]/gm,"");const i=p.length-p.length%4;f.push(Buffer.from(p.slice(0,i),"base64"));p=p.slice(i)}));g.on("end",(()=>{f.push(Buffer.from(p,"base64"));i.append(A,new V(f,E,{type:C}))}))}else{g.on("data",(A=>{f.push(A)}));g.on("end",(()=>{i.append(A,new V(f,E,{type:C}))}))}}));const p=new Promise(((A,i)=>{g.on("finish",A);g.on("error",(A=>i(new TypeError(A))))}));if(this.body!==null)for await(const A of consumeBody(this[D].body))g.write(A);g.end();await p;return i}else if(/application\/x-www-form-urlencoded/.test(i)){let A;try{let i="";const g=new TextDecoder("utf-8",{ignoreBOM:true});for await(const A of consumeBody(this[D].body)){if(!M(A)){throw new TypeError("Expected Uint8Array chunk")}i+=g.decode(A,{stream:true})}i+=g.decode();A=new URLSearchParams(i)}catch(A){throw Object.assign(new TypeError,{cause:A})}const i=new b;for(const[g,E]of A){i.append(g,E)}return i}else{await Promise.resolve();throwIfAborted(this[D]);throw R.errors.exception({header:`${A.name}.formData`,message:"Could not parse content as FormData."})}}};return i}function mixinBody(A){Object.assign(A.prototype,bodyMixinMethods(A))}async function specConsumeBody(A,i,g){R.brandCheck(A,g);throwIfAborted(A[D]);if(bodyUnusable(A[D].body)){throw new TypeError("Body is unusable")}const E=I();const errorSteps=A=>E.reject(A);const successSteps=A=>{try{E.resolve(i(A))}catch(A){errorSteps(A)}};if(A[D].body==null){successSteps(new Uint8Array);return E.promise}await y(A[D].body,successSteps,errorSteps);return E.promise}function bodyUnusable(A){return A!=null&&(A.stream.locked||p.isDisturbed(A.stream))}function utf8DecodeBytes(A){if(A.length===0){return""}if(A[0]===239&&A[1]===187&&A[2]===191){A=A.subarray(3)}const i=j.decode(A);return i}function parseJSONFromBytes(A){return JSON.parse(utf8DecodeBytes(A))}function bodyMimeType(A){const{headersList:i}=A[D];const g=i.get("content-Line truncated
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */C[p-4]=this.maskKey[0];C[p-3]=this.maskKey[1];C[p-2]=this.maskKey[2];C[p-1]=this.maskKey[3];C[1]=g;if(g===126){C.writeUInt16BE(i,2)}else if(g===127){C[2]=C[3]=0;C.writeUIntBE(i,4,6)}C[1]|=128;for(let A=0;A<i;A++){C[p+A]=this.frameData[A]^this.maskKey[A%4]}return C}}A.exports={WebsocketFrameSend:WebsocketFrameSend}},4648:(A,i,g)=>{const{Writable:E}=g(2203);const p=g(1637);const{parserStates:C,opcodes:f,states:Q,emptyBuffer:B}=g(8420);const{kReadyState:I,kSentClose:y,kResponse:b,kReceivedClose:D}=g(4327);const{isValidStatusCode:R,failWebsocketConnection:k,websocketMessageReceived:S}=g(8829);const{WebsocketFrameSend:v}=g(2308);const F={};F.ping=p.channel("undici:websocket:ping");F.pong=p.channel("undici:websocket:pong");class ByteParser extends E{#n=[];#o=0;#a=C.INFO;#c={};#l=[];constructor(A){super();this.ws=A}_write(A,i,g){this.#n.push(A);this.#o+=A.length;this.run(g)}run(A){while(true){if(this.#a===C.INFO){if(this.#o<2){return A()}const i=this.consume(2);this.#c.fin=(i[0]&128)!==0;this.#c.opcode=i[0]&15;this.#c.originalOpcode??=this.#c.opcode;this.#c.fragmented=!this.#c.fin&&this.#c.opcode!==f.CONTINUATION;if(this.#c.fragmented&&this.#c.opcode!==f.BINARY&&this.#c.opcode!==f.TEXT){k(this.ws,"Invalid frame type was fragmented.");return}const g=i[1]&127;if(g<=125){this.#c.payloadLength=g;this.#a=C.READ_DATA}else if(g===126){this.#a=C.PAYLOADLENGTH_16}else if(g===127){this.#a=C.PAYLOADLENGTH_64}if(this.#c.fragmented&&g>125){k(this.ws,"Fragmented frame exceeded 125 bytes.");return}else if((this.#c.opcode===f.PING||this.#c.opcode===f.PONG||this.#c.opcode===f.CLOSE)&&g>125){k(this.ws,"Payload length for control frame exceeded 125 bytes.");return}else if(this.#c.opcode===f.CLOSE){if(g===1){k(this.ws,"Received close frame with a 1-byte body.");return}const A=this.consume(g);this.#c.closeInfo=this.parseCloseBody(false,A);if(!this.ws[y]){const A=Buffer.allocUnsafe(2);A.writeUInt16BE(this.#c.closeInfo.code,0);const i=new v(A);this.ws[b].socket.write(i.createFrame(f.CLOSE),(A=>{if(!A){this.ws[y]=true}}))}this.ws[I]=Q.CLOSING;this.ws[D]=true;this.end();return}else if(this.#c.opcode===f.PING){const i=this.consume(g);if(!this.ws[D]){const A=new v(i);this.ws[b].socket.write(A.createFrame(f.PONG));if(F.ping.hasSubscribers){F.ping.publish({payload:i})}}this.#a=C.INFO;if(this.#o>0){continue}else{A();return}}else if(this.#c.opcode===f.PONG){const i=this.consume(g);if(F.pong.hasSubscribers){F.pong.publish({payload:i})}if(this.#o>0){continue}else{A();return}}}else if(this.#a===C.PAYLOADLENGTH_16){if(this.#o<2){return A()}const i=this.consume(2);this.#c.payloadLength=i.readUInt16BE(0);this.#a=C.READ_DATA}else if(this.#a===C.PAYLOADLENGTH_64){if(this.#o<8){return A()}const i=this.consume(8);const g=i.readUInt32BE(0);if(g>2**31-1){k(this.ws,"Received payload length > 2^31 bytes.");return}const E=i.readUInt32BE(4);this.#c.payloadLength=(g<<8)+E;this.#a=C.READ_DATA}else if(this.#a===C.READ_DATA){if(this.#o<this.#c.payloadLength){return A()}else if(this.#o>=this.#c.payloadLength){const A=this.consume(this.#c.payloadLength);this.#l.push(A);if(!this.#c.fragmented||this.#c.fin&&this.#c.opcode===f.CONTINUATION){const A=Buffer.concat(this.#l);S(this.ws,this.#c.originalOpcode,A);this.#c={};this.#l.length=0}this.#a=C.INFO}}if(this.#o>0){continue}else{A();break}}}consume(A){if(A>this.#o){return null}else if(A===0){return B}if(this.#n[0].length===A){this.#o-=this.#n[0].length;return this.#n.shift()}const i=Buffer.allocUnsafe(A);let g=0;while(g!==A){const E=this.#n[0];const{length:p}=E;if(p+g===A){i.set(this.#n.shift(),g);break}else if(p+g>A){i.set(E.subarray(0,A-g),g);this.#n[0]=E.subarray(A-g);break}else{i.set(this.#n.shift(),g);g+=E.length}}this.#o-=A;return i}parseCloseBody(A,i){let g;if(i.length>=2){g=i.readUInt16BE(0)}if(A){if(!R(g)){return null}return{code:g}}let E=i.subarray(2);if(E[0]===239&&E[1]===187&&E[2]===191){E=E.subarray(3)}if(g!==undefined&&!R(g)){return null}try{E=new TextDecoder("utf-8",{fatal:true}).decode(E)}catch{return null}return{code:g,reason:E}}get closingInfo(){return this.#c.closeInfo}}A.exports={ByteParser:ByteParser}},4327:A=>{A.exports={kWebSocketURL:Symbol("url"),kReadyState:Symbol("ready state"),kController:Symbol("controller"),kResponse:Symbol("response"),kBinaryType:Symbol("binary type"),kSentClose:Symbol("sent close"),kReceivedClose:Symbol("received close"),kByteParser:Symbol("byte parser")}},8829:(A,i,g)=>{const{kReadyState:E,kController:p,kResponse:C,kBinaryType:f,kWebSocketURL:Q}=g(4327);const{states:B,opcodes:I}=g(8420);const{MessageEvent:y,ErrorEvent:b}=g(6544);function isEstablished(A){return A[E]===B.OPEN}function isClosing(A){return A[E]===B.CLOSING}function isClosed(A){return A[E]===B.CLOSED}function fireEvent(A,i,g=Event,E){const p=new g(A,E);i.dispatchEvent(p)}function websocketMessageReceived(A,i,g){if(A[E]!==B.OPEN){return}let p;if(i===I.TEXT){try{p=new TextDecoder("utf-8",{fatal:true}).decode(g)}catch{failWebsocketConnection(A,"ReceiLine truncated
/*! formdata-polyfill. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */;const escape=A=>A.replace(/\n/g,"%0A").replace(/\r/g,"%0D").replace(/"/g,"%22");const normalizeLinefeeds=A=>A.replace(/\r?\n|\r/g,"\r\n");const E=[];const p=new Uint8Array([13,10]);y=0;let C=false;for(const[i,f]of A){if(typeof f==="string"){const A=H.encode(g+`; name="${escape(normalizeLinefeeds(i))}"`+`\r\n\r\n${normalizeLinefeeds(f)}\r\n`);E.push(A);y+=A.byteLength}else{const A=H.encode(`${g}; name="${escape(normalizeLinefeeds(i))}"`+(f.name?`; filename="${escape(f.name)}"`:"")+"\r\n"+`Content-Type: ${f.type||"application/octet-stream"}\r\n\r\n`);E.push(A,f,p);if(typeof f.size==="number"){y+=A.byteLength+f.size+p.byteLength}else{C=true}}}const f=H.encode(`--${i}--\r\n`);E.push(f);y+=f.byteLength;if(C){y=null}I=A;B=async function*(){for(const A of E){if(A.stream){yield*A.stream()}else{yield A}}};b=`multipart/form-data; boundary=${i}`}else if(C(A)){I=A;y=A.size;if(A.type){b=A.type}}else if(typeof A[Symbol.asyncIterator]==="function"){if(i){throw new TypeError("keepalive")}if(E.isDisturbed(A)||A.locked){throw new TypeError("Response body object should not be disturbed or locked")}g=A instanceof ReadableStream?A:p(A)}if(typeof I==="string"||E.isBuffer(I)){y=Buffer.byteLength(I)}if(B!=null){let i;g=new ReadableStream({async start(){i=B(A)[Symbol.asyncIterator]()},async pull(A){const{value:E,done:p}=await i.next();if(p){queueMicrotask((()=>{A.close();A.byobRequest?.respond(0)}))}else{if(!F(g)){const i=new Uint8Array(E);if(i.byteLength){A.enqueue(i)}}}return A.desiredSize>0},async cancel(A){await i.return()},type:"bytes"})}const D={stream:g,source:I,length:y};return[D,b]}function safelyExtractBody(A,i=false){if(A instanceof ReadableStream){v(!E.isDisturbed(A),"The body has already been consumed.");v(!A.locked,"The stream is locked.")}return extractBody(A,i)}function cloneBody(A,i){const[g,E]=i.stream.tee();i.stream=g;return{stream:E,length:i.length,source:i.source}}function throwIfAborted(A){if(A.aborted){throw new DOMException("The operation was aborted.","AbortError")}}function bodyMixinMethods(A){const i={blob(){return consumeBody(this,(A=>{let i=bodyMimeType(this);if(i===null){i=""}else if(i){i=L(i)}return new S([A],{type:i})}),A)},arrayBuffer(){return consumeBody(this,(A=>new Uint8Array(A).buffer),A)},text(){return consumeBody(this,b,A)},json(){return consumeBody(this,parseJSONFromBytes,A)},formData(){return consumeBody(this,(A=>{const i=bodyMimeType(this);if(i!==null){switch(i.essence){case"multipart/form-data":{const g=M(A,i);if(g==="failure"){throw new TypeError("Failed to parse body as FormData.")}const E=new D;E[R]=g;return E}case"application/x-www-form-urlencoded":{const i=new URLSearchParams(A.toString());const g=new D;for(const[A,E]of i){g.append(A,E)}return g}}}throw new TypeError('Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".')}),A)},bytes(){return consumeBody(this,(A=>new Uint8Array(A)),A)}};return i}function mixinBody(A){Object.assign(A.prototype,bodyMixinMethods(A))}async function consumeBody(A,i,g){k.brandCheck(A,g);if(bodyUnusable(A)){throw new TypeError("Body is unusable: Body has already been read")}throwIfAborted(A[R]);const E=B();const errorSteps=A=>E.reject(A);const successSteps=A=>{try{E.resolve(i(A))}catch(A){errorSteps(A)}};if(A[R].body==null){successSteps(Buffer.allocUnsafe(0));return E.promise}await I(A[R].body,successSteps,errorSteps);return E.promise}function bodyUnusable(A){const i=A[R].body;return i!=null&&(i.stream.locked||E.isDisturbed(i.stream))}function parseJSONFromBytes(A){return JSON.parse(b(A))}function bodyMimeType(A){const i=A[R].headersList;const g=y(i);if(g==="failure"){return null}return g}A.exports={extractBody:extractBody,safelyExtractBody:safelyExtractBody,cloneBody:cloneBody,mixinBody:mixinBody,streamRegistry:Y,hasFinalizationRegistry:G,bodyUnusable:bodyUnusable}},5106:A=>{const i=["GET","HEAD","POST"];const g=new Set(i);const E=[101,204,205,304];const p=[301,302,303,307,308];const C=new Set(p);const f=["1","7","9","11","13","15","17","19","20","21","22","23","25","37","42","43","53","69","77","79","87","95","101","102","103","104","109","110","111","113","115","117","119","123","135","137","139","143","161","179","389","427","465","512","513","514","515","526","530","531","532","540","548","554","556","563","587","601","636","989","990","993","995","1719","1720","1723","2049","3659","4045","4190","5060","5061","6000","6566","6665","6666","6667","6668","6669","6679","6697","10080"];const Q=new Set(f);const B=["","no-referrer","no-referrer-when-downgrade","same-origin","origin","strict-origin","origin-when-cross-origin","strict-origin-when-cross-origin","unsafe-url"];const I=new Set(B);const y=["follow","manual","error"];const b=["GET","HEAD","OPTIONS","TRACE"];const D=new Set(b);const R=["navigate","same-origin","no-cors","cors"];const k=["omit","same-origin","include"];const S=["default","no-store","reload","no-cache","force-cLine truncated
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */Q[f-4]=g[0];Q[f-3]=g[1];Q[f-2]=g[2];Q[f-1]=g[3];Q[1]=C;if(C===126){Q.writeUInt16BE(p,2)}else if(C===127){Q[2]=Q[3]=0;Q.writeUIntBE(p,4,6)}Q[1]|=128;for(let A=0;A<p;++A){Q[f+A]=i[A]^g[A&3]}return Q}}A.exports={WebsocketFrameSend:WebsocketFrameSend}},958:(A,i,g)=>{const{createInflateRaw:E,Z_DEFAULT_WINDOWBITS:p}=g(8522);const{isValidClientWindowBits:C}=g(4530);const{MessageSizeExceededError:f}=g(1366);const Q=Buffer.from([0,0,255,255]);const B=Symbol("kBuffer");const I=Symbol("kLength");class PerMessageDeflate{#re;#S={};#ie=0;constructor(A,i){this.#S.serverNoContextTakeover=A.has("server_no_context_takeover");this.#S.serverMaxWindowBits=A.get("server_max_window_bits");this.#ie=i.maxPayloadSize}decompress(A,i,g){if(!this.#re){let A=p;if(this.#S.serverMaxWindowBits){if(!C(this.#S.serverMaxWindowBits)){g(new Error("Invalid server_max_window_bits"));return}A=Number.parseInt(this.#S.serverMaxWindowBits)}try{this.#re=E({windowBits:A})}catch(A){g(A);return}this.#re[B]=[];this.#re[I]=0;this.#re.on("data",(A=>{this.#re[I]+=A.length;if(this.#ie>0&&this.#re[I]>this.#ie){g(new f);this.#re.removeAllListeners();this.#re=null;return}this.#re[B].push(A)}));this.#re.on("error",(A=>{this.#re=null;g(A)}))}this.#re.write(A);if(i){this.#re.write(Q)}this.#re.flush((()=>{if(!this.#re){return}const A=Buffer.concat(this.#re[B],this.#re[I]);this.#re[B].length=0;this.#re[I]=0;g(null,A)}))}}A.exports={PerMessageDeflate:PerMessageDeflate}},5623:(A,i,g)=>{const{Writable:E}=g(7075);const p=g(4589);const{parserStates:C,opcodes:f,states:Q,emptyBuffer:B,sentCloseFrameState:I}=g(8989);const{kReadyState:y,kSentClose:b,kResponse:D,kReceivedClose:R}=g(9081);const{channels:k}=g(5997);const{isValidStatusCode:S,isValidOpcode:v,failWebsocketConnection:F,websocketMessageReceived:x,utf8Decode:U,isControlFrame:L,isTextBinaryFrame:M,isContinuationFrame:T}=g(4530);const{WebsocketFrameSend:H}=g(9177);const{closeWebSocketConnection:G}=g(2930);const{PerMessageDeflate:Y}=g(958);const{MessageSizeExceededError:J}=g(1366);function failWebsocketConnectionWithCode(A,i,g){G(A,i,g,Buffer.byteLength(g));F(A,g)}class ByteParser extends E{#n=[];#ne=0;#o=0;#oe=false;#a=C.INFO;#c={};#l=[];#d;#ae;#ie;constructor(A,i,g={}){super();this.ws=A;this.#d=i==null?new Map:i;this.#ae=g.maxFragments??0;this.#ie=g.maxPayloadSize??0;if(this.#d.has("permessage-deflate")){this.#d.set("permessage-deflate",new Y(i,g))}}_write(A,i,g){this.#n.push(A);this.#o+=A.length;this.#oe=true;this.run(g)}#ce(){if(this.#ie>0&&!L(this.#c.opcode)&&this.#c.payloadLength+this.#ne>this.#ie){failWebsocketConnectionWithCode(this.ws,1009,"Payload size exceeds maximum allowed size");return false}return true}run(A){while(this.#oe){if(this.#a===C.INFO){if(this.#o<2){return A()}const i=this.consume(2);const g=(i[0]&128)!==0;const E=i[0]&15;const p=(i[1]&128)===128;const Q=!g&&E!==f.CONTINUATION;const B=i[1]&127;const I=i[0]&64;const y=i[0]&32;const b=i[0]&16;if(!v(E)){F(this.ws,"Invalid opcode received");return A()}if(p){F(this.ws,"Frame cannot be masked");return A()}if(I!==0&&!this.#d.has("permessage-deflate")){F(this.ws,"Expected RSV1 to be clear.");return}if(y!==0||b!==0){F(this.ws,"RSV1, RSV2, RSV3 must be clear");return}if(Q&&!M(E)){F(this.ws,"Invalid frame type was fragmented.");return}if(M(E)&&this.#l.length>0){F(this.ws,"Expected continuation frame");return}if(this.#c.fragmented&&Q){F(this.ws,"Fragmented frame exceeded 125 bytes.");return}if((B>125||Q)&&L(E)){F(this.ws,"Control frame either too large or fragmented");return}if(T(E)&&this.#l.length===0&&!this.#c.compressed){F(this.ws,"Unexpected continuation frame");return}if(B<=125){this.#c.payloadLength=B;this.#a=C.READ_DATA;if(!this.#ce()){return}}else if(B===126){this.#a=C.PAYLOADLENGTH_16}else if(B===127){this.#a=C.PAYLOADLENGTH_64}if(M(E)){this.#c.binaryType=E;this.#c.compressed=I!==0}this.#c.opcode=E;this.#c.masked=p;this.#c.fin=g;this.#c.fragmented=Q}else if(this.#a===C.PAYLOADLENGTH_16){if(this.#o<2){return A()}const i=this.consume(2);this.#c.payloadLength=i.readUInt16BE(0);this.#a=C.READ_DATA;if(!this.#ce()){return}}else if(this.#a===C.PAYLOADLENGTH_64){if(this.#o<8){return A()}const i=this.consume(8);const g=i.readUInt32BE(0);const E=i.readUInt32BE(4);if(g!==0||E>2**31-1){F(this.ws,"Received payload length > 2^31 bytes.");return}this.#c.payloadLength=E;this.#a=C.READ_DATA;if(!this.#ce()){return}}else if(this.#a===C.READ_DATA){if(this.#o<this.#c.payloadLength){return A()}const i=this.consume(this.#c.payloadLength);if(L(this.#c.opcode)){this.#oe=this.parseControlFrame(i);this.#a=C.INFO}else{if(!this.#c.compressed){if(!this.writeFragments(i)){return}if(this.#ie>0&&this.#ne>this.#ie){failWebsocketConnectionWithCode(this.ws,1009,(new J).message);return}if(!this.#c.fragmented&&this.#c.fin){x(this.ws,this.#c.binaryType,this.consumeFragments())}this.#a=C.INFO}else{this.#d.get("permessage-deflate").decompress(i,this.#c.fin,((i,g)=>{if(i){const A=i instanceof J?1009:1007;failWebsocketConnectionLine truncated
/*! ws. MIT License. Einar Otto Stangvik <einaros@gmail.com> */Q[f-4]=g[0];Q[f-3]=g[1];Q[f-2]=g[2];Q[f-1]=g[3];Q[1]=C;if(C===126){Q.writeUInt16BE(p,2)}else if(C===127){Q[2]=Q[3]=0;Q.writeUIntBE(p,4,6)}Q[1]|=128;for(let A=0;A<p;++A){Q[f+A]=i[A]^g[A&3]}return Q}}A.exports={WebsocketFrameSend:WebsocketFrameSend}},958:(A,i,g)=>{const{createInflateRaw:E,Z_DEFAULT_WINDOWBITS:p}=g(8522);const{isValidClientWindowBits:C}=g(4530);const{MessageSizeExceededError:f}=g(1366);const Q=Buffer.from([0,0,255,255]);const B=Symbol("kBuffer");const I=Symbol("kLength");class PerMessageDeflate{#re;#S={};#ie=0;constructor(A,i){this.#S.serverNoContextTakeover=A.has("server_no_context_takeover");this.#S.serverMaxWindowBits=A.get("server_max_window_bits");this.#ie=i.maxPayloadSize}decompress(A,i,g){if(!this.#re){let A=p;if(this.#S.serverMaxWindowBits){if(!C(this.#S.serverMaxWindowBits)){g(new Error("Invalid server_max_window_bits"));return}A=Number.parseInt(this.#S.serverMaxWindowBits)}try{this.#re=E({windowBits:A})}catch(A){g(A);return}this.#re[B]=[];this.#re[I]=0;this.#re.on("data",(A=>{this.#re[I]+=A.length;if(this.#ie>0&&this.#re[I]>this.#ie){g(new f);this.#re.removeAllListeners();this.#re=null;return}this.#re[B].push(A)}));this.#re.on("error",(A=>{this.#re=null;g(A)}))}this.#re.write(A);if(i){this.#re.write(Q)}this.#re.flush((()=>{if(!this.#re){return}const A=Buffer.concat(this.#re[B],this.#re[I]);this.#re[B].length=0;this.#re[I]=0;g(null,A)}))}}A.exports={PerMessageDeflate:PerMessageDeflate}},5623:(A,i,g)=>{const{Writable:E}=g(7075);const p=g(4589);const{parserStates:C,opcodes:f,states:Q,emptyBuffer:B,sentCloseFrameState:I}=g(8989);const{kReadyState:y,kSentClose:b,kResponse:D,kReceivedClose:R}=g(9081);const{channels:k}=g(5997);const{isValidStatusCode:S,isValidOpcode:v,failWebsocketConnection:F,websocketMessageReceived:x,utf8Decode:U,isControlFrame:L,isTextBinaryFrame:M,isContinuationFrame:T}=g(4530);const{WebsocketFrameSend:H}=g(9177);const{closeWebSocketConnection:G}=g(2930);const{PerMessageDeflate:Y}=g(958);const{MessageSizeExceededError:J}=g(1366);function failWebsocketConnectionWithCode(A,i,g){G(A,i,g,Buffer.byteLength(g));F(A,g)}class ByteParser extends E{#n=[];#ne=0;#o=0;#oe=false;#a=C.INFO;#c={};#l=[];#d;#ae;#ie;constructor(A,i,g={}){super();this.ws=A;this.#d=i==null?new Map:i;this.#ae=g.maxFragments??0;this.#ie=g.maxPayloadSize??0;if(this.#d.has("permessage-deflate")){this.#d.set("permessage-deflate",new Y(i,g))}}_write(A,i,g){this.#n.push(A);this.#o+=A.length;this.#oe=true;this.run(g)}#ce(){if(this.#ie>0&&!L(this.#c.opcode)&&this.#c.payloadLength+this.#ne>this.#ie){failWebsocketConnectionWithCode(this.ws,1009,"Payload size exceeds maximum allowed size");return false}return true}run(A){while(this.#oe){if(this.#a===C.INFO){if(this.#o<2){return A()}const i=this.consume(2);const g=(i[0]&128)!==0;const E=i[0]&15;const p=(i[1]&128)===128;const Q=!g&&E!==f.CONTINUATION;const B=i[1]&127;const I=i[0]&64;const y=i[0]&32;const b=i[0]&16;if(!v(E)){F(this.ws,"Invalid opcode received");return A()}if(p){F(this.ws,"Frame cannot be masked");return A()}if(I!==0&&!this.#d.has("permessage-deflate")){F(this.ws,"Expected RSV1 to be clear.");return}if(y!==0||b!==0){F(this.ws,"RSV1, RSV2, RSV3 must be clear");return}if(Q&&!M(E)){F(this.ws,"Invalid frame type was fragmented.");return}if(M(E)&&this.#l.length>0){F(this.ws,"Expected continuation frame");return}if(this.#c.fragmented&&Q){F(this.ws,"Fragmented frame exceeded 125 bytes.");return}if((B>125||Q)&&L(E)){F(this.ws,"Control frame either too large or fragmented");return}if(T(E)&&this.#l.length===0&&!this.#c.compressed){F(this.ws,"Unexpected continuation frame");return}if(B<=125){this.#c.payloadLength=B;this.#a=C.READ_DATA;if(!this.#ce()){return}}else if(B===126){this.#a=C.PAYLOADLENGTH_16}else if(B===127){this.#a=C.PAYLOADLENGTH_64}if(M(E)){this.#c.binaryType=E;this.#c.compressed=I!==0}this.#c.opcode=E;this.#c.masked=p;this.#c.fin=g;this.#c.fragmented=Q}else if(this.#a===C.PAYLOADLENGTH_16){if(this.#o<2){return A()}const i=this.consume(2);this.#c.payloadLength=i.readUInt16BE(0);this.#a=C.READ_DATA;if(!this.#ce()){return}}else if(this.#a===C.PAYLOADLENGTH_64){if(this.#o<8){return A()}const i=this.consume(8);const g=i.readUInt32BE(0);const E=i.readUInt32BE(4);if(g!==0||E>2**31-1){F(this.ws,"Received payload length > 2^31 bytes.");return}this.#c.payloadLength=E;this.#a=C.READ_DATA;if(!this.#ce()){return}}else if(this.#a===C.READ_DATA){if(this.#o<this.#c.payloadLength){return A()}const i=this.consume(this.#c.payloadLength);if(L(this.#c.opcode)){this.#oe=this.parseControlFrame(i);this.#a=C.INFO}else{if(!this.#c.compressed){if(!this.writeFragments(i)){return}if(this.#ie>0&&this.#ne>this.#ie){failWebsocketConnectionWithCode(this.ws,1009,(new J).message);return}if(!this.#c.fragmented&&this.#c.fin){x(this.ws,this.#c.binaryType,this.consumeFragments())}this.#a=C.INFO}else{this.#d.get("permessage-deflate").decompress(i,this.#c.fin,((i,g)=>{if(i){const A=i instanceof J?1009:1007;failWebsocketConnectionLine truncated
+82 -25
View File
@@ -118,17 +118,19 @@ export function validateAuthentication(token, username, password) {
* @param {'ignore'|'warn'|'fail'} action
* @param {boolean} flag - Only acts when true
* @param {string} message - Passed to core.warning / core.setFailed
* @returns {boolean} - Returns true if the action was 'fail' and the flag was true.
*/
export function applyOnAction(action, flag, message) {
if (!flag) return;
if (!flag) return false;
if (action === "warn") {
core.warning(message);
return;
return false;
}
if (action === "fail") {
core.setFailed(message);
return true;
}
// 'ignore' → no-op
return false;
}
/**
@@ -179,33 +181,72 @@ export async function postCode() {
return;
}
let api = new ScreepsAPI(login_arguments);
if (token) {
if (!token) {
core.info(`Logging in as user ${username}`);
try {
await api.auth(username, password, login_arguments);
} catch (err) {
core.error(`Authentication error: ${err}`);
throw err;
}
}
let oldCode = null;
let rollbackOnFailure = false;
try {
rollbackOnFailure = core.getBooleanInput("rollback_on_failure");
} catch (e) {
// getBooleanInput throws if not 'true' or 'false', ignore
}
if (rollbackOnFailure) {
core.info(
`Downloading existing code from branch ${branch} for potential rollback...`,
);
try {
const getResponse = await api.code.get(branch);
if (getResponse && getResponse.ok && getResponse.modules) {
oldCode = getResponse.modules;
core.info(
`Successfully downloaded existing code (modules: ${Object.keys(oldCode).join(", ")})`,
);
} else {
core.setFailed(
`Failed to download existing code, but rollback_on_failure is enabled. Aborting deployment.`,
);
return;
}
} catch (err) {
core.setFailed(
`Error downloading existing code: ${err.message}. Aborting deployment.`,
);
return;
}
}
try {
const response = await api.code.set(branch, files_to_push);
core.info(JSON.stringify(response, null, 2));
core.info(`Code set successfully to ${branch}`);
} else {
core.info(`Logging in as user ${username}`);
await Promise.resolve()
.then(() => api.auth(username, password, login_arguments))
.then(() => api.code.set(branch, files_to_push))
.then(() => {
core.info(`Code set successfully to ${branch}`);
})
.catch((err) => {
core.error(`Upload error: ${err}`);
throw err;
});
} catch (err) {
core.error(`Upload error: ${err}`);
throw err;
}
// Console monitoring (optional)
const monitorTicks = parseInt(core.getInput("monitor") || "0", 10);
if (monitorTicks > 0) {
const onTraceback = core.getInput("on_traceback") || "fail";
const onErrorLog = core.getInput("on_error_log") || "warn";
const onWarningLog = core.getInput("on_warning_log") || "ignore";
const result = await monitorConsole(api, {
monitor: monitorTicks,
logToFile: core.getBooleanInput("log_to_file"),
onTraceback: core.getInput("on_traceback") || "fail",
onErrorLog: core.getInput("on_error_log") || "warn",
onWarningLog: core.getInput("on_warning_log") || "ignore",
onTraceback,
onErrorLog,
onWarningLog,
monitorInterval: parseInt(core.getInput("monitor_interval") || "10", 10),
hostname,
shard: core.getInput("shard") || undefined,
@@ -215,21 +256,37 @@ export async function postCode() {
core.setOutput("saw_error_log", String(result.sawErrorLog));
core.setOutput("saw_warning_log", String(result.sawWarningLog));
applyOnAction(
core.getInput("on_traceback"),
const fail1 = applyOnAction(
onTraceback,
result.sawTraceback,
"Screeps console: traceback detected",
);
applyOnAction(
core.getInput("on_error_log"),
const fail2 = applyOnAction(
onErrorLog,
result.sawErrorLog,
"Screeps console: error log output detected",
);
applyOnAction(
core.getInput("on_warning_log"),
const fail3 = applyOnAction(
onWarningLog,
result.sawWarningLog,
"Screeps console: warning log output detected",
);
const shouldFail = fail1 || fail2 || fail3;
if (shouldFail && rollbackOnFailure && oldCode) {
core.info(
"Action failed based on monitor configuration. Rolling back to previous code...",
);
try {
await api.code.set(branch, oldCode);
core.info(
`Successfully rolled back to previous code on branch ${branch}.`,
);
} catch (err) {
core.error(`Rollback failed: ${err}`);
}
}
}
}