Author SHA1 Message Date
Renovate a022bbbb17 fix(deps): update dependency screeps-api to v2
renovate/artifacts Artifact file update failure
renovate/stability-days Updates have met minimum release age requirement
Lint / pre-commit Linting (push) Has been cancelled
Test / Run Tests (push) Has been cancelled
2026-06-19 23:18:12 +00:00
RenovateandRenovate 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
RenovateandRenovate 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
RenovateandPhilipp 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
RenovateandPhilipp 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
RenovateandRenovate 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
RenovateandRenovate 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
RenovateandRenovate 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
Philipp 076e96f3de fix(monitor): use global console channel and implement shard filtering (#87)
Lint / pre-commit Linting (push) Successful in 47s
Test / Run Tests (push) Successful in 1m3s
This PR fixes the issue where console monitoring was empty when a specific shard was targeted on the official Screeps server.

### Changes:
- **Unified Subscription**: Changed WebSocket subscription path from `shard/console` (invalid) to the global `console` channel.
- **Shard Filtering**: Implemented client-side filtering in `handleConsoleEvent` to only display logs matching the targeted shard.
- **Precise Timing**: Retained the shard-specific `api.time()` call for accurate tick duration tracking.
- **Tests**: Added and updated 48 unit tests verifying the fix and shard filtering logic.
- **Distribution**: Rebuilt `dist/index.js` and bumped version to `v0.2.1`.

Reviewed-on: #87
2026-05-16 22:29:24 +02:00
Philipp bf52580bf3 feat: add Screeps console monitoring with configurable error handling and shard support (#84)
Lint / pre-commit Linting (push) Successful in 1m5s
Test / Run Tests (push) Successful in 1m23s
Reviewed-on: #84
2026-05-16 19:44:39 +02:00
Philipp be3c8ac7d2 feat: add CODEOWNERS file (#85)
Lint / pre-commit Linting (push) Successful in 41s
Test / Run Tests (push) Successful in 1m8s
This PR adds the `.gitea/CODEOWNERS` file to assign `@AutoReview` to all files.

Reviewed-on: #85
2026-05-16 17:14:31 +02:00
RenovateandPhilipp 1df4a4248c chore(deps): update vitest monorepo to v4.1.6 (#82)
Lint / pre-commit Linting (push) Successful in 45s
Test / Run Tests (push) Successful in 4m13s
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.5` → `4.1.6`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.1.5/4.1.6) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.1.6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.1.5/4.1.6?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.1.5` → `4.1.6`](https://renovatebot.com/diffs/npm/vitest/4.1.5/4.1.6) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.1.5/4.1.6?slim=true) |

---

### Release Notes

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

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

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

#####    🐞 Bug Fixes

- **browser**: Provide project reference in `ToMatchScreenshotResolvePath`  -  by [@&#8203;macarie](https://github.com/macarie) and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10138](https://github.com/vitest-dev/vitest/issues/10138) [<samp>(31882)</samp>](https://github.com/vitest-dev/vitest/commit/31882607c)
- Global `sequence.concurrent: true` with top-level `test(..., { concurrent: false })` + depreacte `sequential` test API and options  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **Codex** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10196](https://github.com/vitest-dev/vitest/issues/10196) [<samp>(2847d)</samp>](https://github.com/vitest-dev/vitest/commit/2847dfa2a)
- **browser**: Simplify orchestrator otel carrier  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;10285](https://github.com/vitest-dev/vitest/issues/10285) [<samp>(18af9)</samp>](https://github.com/vitest-dev/vitest/commit/18af98cee)

#####    🏎 Performance

- Stringify diff objects only once  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10276](https://github.com/vitest-dev/vitest/issues/10276) [<samp>(9f7b1)</samp>](https://github.com/vitest-dev/vitest/commit/9f7b1528c)

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

</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:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjkuNSIsInVwZGF0ZWRJblZlciI6IjQzLjE2OS41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0aW9uIiwibnBtIl19-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/82
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-05-16 12:40:04 +02:00
RenovateandRenovate 2a4928efe1 chore(deps): update pre-commit hook python-jsonschema/check-jsonschema to v0.37.2 (#81)
Lint / pre-commit Linting (push) Successful in 36s
Test / Run Tests (push) Successful in 1m5s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-05-08 02:14:39 +02:00
RenovateandPhilipp 065bdde05d chore(deps): update dependency @actions/core to v3.0.1 (#80)
Lint / pre-commit Linting (push) Successful in 32s
Test / Run Tests (push) Successful in 35s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@actions/core](https://github.com/actions/toolkit/tree/main/packages/core) ([source](https://github.com/actions/toolkit/tree/HEAD/packages/core)) | [`3.0.0` → `3.0.1`](https://renovatebot.com/diffs/npm/@actions%2fcore/3.0.0/3.0.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@actions%2fcore/3.0.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@actions%2fcore/3.0.0/3.0.1?slim=true) |

---

### Release Notes

<details>
<summary>actions/toolkit (@&#8203;actions/core)</summary>

### [`v3.0.1`](https://github.com/actions/toolkit/blob/HEAD/packages/core/RELEASES.md#301)

- Bump `undici` from `6.23.0` to `6.24.1` [#&#8203;2348](https://github.com/actions/toolkit/pull/2348)

</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 this update again.

---

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

---

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

Reviewed-on: #80
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-04-25 14:13:12 +02:00
RenovateandPhilipp 26a3dacde8 chore(deps): update vitest monorepo to v4.1.5 (#79)
Lint / pre-commit Linting (push) Successful in 30s
Test / Run Tests (push) Successful in 36s
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.4` → `4.1.5`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.1.4/4.1.5) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.1.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.1.4/4.1.5?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.1.4` → `4.1.5`](https://renovatebot.com/diffs/npm/vitest/4.1.4/4.1.5) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.1.4/4.1.5?slim=true) |

---

### Release Notes

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

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

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

#####    🚀 Experimental Features

- **coverage**: Istanbul to support `instrumenter` option  -  by [@&#8203;BartWaardenburg](https://github.com/BartWaardenburg) and [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;10119](https://github.com/vitest-dev/vitest/issues/10119) [<samp>(0e0ff)</samp>](https://github.com/vitest-dev/vitest/commit/0e0ff41c7)

#####    🐞 Bug Fixes

- \--project negation excludes browser instances  -  by [@&#8203;felamaslen](https://github.com/felamaslen) in [#&#8203;10131](https://github.com/vitest-dev/vitest/issues/10131) [<samp>(9423d)</samp>](https://github.com/vitest-dev/vitest/commit/9423dc084)
- Project color label on html reporter  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;10142](https://github.com/vitest-dev/vitest/issues/10142) [<samp>(596f7)</samp>](https://github.com/vitest-dev/vitest/commit/596f73986)
- Fix `vi.defineHelper` called as object method  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;10163](https://github.com/vitest-dev/vitest/issues/10163) [<samp>(122c2)</samp>](https://github.com/vitest-dev/vitest/commit/122c25b5b)
- Alias `agent` reporter to `minimal`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10157](https://github.com/vitest-dev/vitest/issues/10157) [<samp>(663b9)</samp>](https://github.com/vitest-dev/vitest/commit/663b99fe3)
- Respect diff config options in soft assertions  -  by [@&#8203;Copilot](https://github.com/Copilot), **sheremet-va** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;8696](https://github.com/vitest-dev/vitest/issues/8696) [<samp>(9787d)</samp>](https://github.com/vitest-dev/vitest/commit/9787dedad)
- Respect diff config options in soft assertions "  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;8696](https://github.com/vitest-dev/vitest/issues/8696) [<samp>(7dc6d)</samp>](https://github.com/vitest-dev/vitest/commit/7dc6d54fd)
- **ast-collect**: Recognize \_*vi\_import* prefix in static test discovery  -  by [@&#8203;Yejneshwar](https://github.com/Yejneshwar) in [#&#8203;10129](https://github.com/vitest-dev/vitest/issues/10129) [<samp>(32546)</samp>](https://github.com/vitest-dev/vitest/commit/325463ab2)
- **coverage**: Descriptive error message when reports directory is removed during test run  -  by [@&#8203;DaveT1991](https://github.com/DaveT1991) and [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;10117](https://github.com/vitest-dev/vitest/issues/10117) [<samp>(14133)</samp>](https://github.com/vitest-dev/vitest/commit/1413382e1)
- **snapshot**: Increase default snapshot max output length  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Codex** in [#&#8203;10150](https://github.com/vitest-dev/vitest/issues/10150) [<samp>(21e66)</samp>](https://github.com/vitest-dev/vitest/commit/21e66ff63)
- **ui**: Fix jsx/tsx syntax highlight  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;10152](https://github.com/vitest-dev/vitest/issues/10152) [<samp>(f1b1f)</samp>](https://github.com/vitest-dev/vitest/commit/f1b1f6c7b)
- **web-worker**: Support MessagePort objects referenced inside postMessage data  -  by [@&#8203;whitphx](https://github.com/whitphx) and **Claude Opus 4.6 (1M context)** in [#&#8203;9927](https://github.com/vitest-dev/vitest/issues/9927) and [#&#8203;10124](https://github.com/vitest-dev/vitest/issues/10124) [<samp>(7ad7d)</samp>](https://github.com/vitest-dev/vitest/commit/7ad7d39af)
- **api**: Make test-specification options writable  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10154](https://github.com/vitest-dev/vitest/issues/10154) [<samp>(6abd5)</samp>](https://github.com/vitest-dev/vitest/commit/6abd557b7)

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

</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 [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMzcuMCIsInVwZGF0ZWRJblZlciI6IjQzLjEzNy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0aW9uIiwibnBtIl19-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/79
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-04-24 13:27:25 +02:00
RenovateandRenovate 11576b5c40 chore(deps): update actions/setup-node digest to 48b55a0 (#78)
Lint / pre-commit Linting (push) Successful in 28s
Test / Run Tests (push) Successful in 2m11s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-04-20 06:30:53 +02:00
RenovateandPhilipp 7f1ea2b452 chore(deps): update vitest monorepo to v4.1.4 (#77)
Lint / pre-commit Linting (push) Successful in 27s
Test / Run Tests (push) Successful in 35s
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.2` → `4.1.4`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.1.2/4.1.4) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.1.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.1.2/4.1.4?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.1.2` → `4.1.4`](https://renovatebot.com/diffs/npm/vitest/4.1.2/4.1.4) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.1.2/4.1.4?slim=true) |

---

### Release Notes

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

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

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

#####    🚀 Experimental Features

- **coverage**:
  - Default to text reporter `skipFull` if agent detected  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;10018](https://github.com/vitest-dev/vitest/issues/10018) [<samp>(53757)</samp>](https://github.com/vitest-dev/vitest/commit/53757804c)
- **experimental**:
  - Expose `assertion` as a public field  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10095](https://github.com/vitest-dev/vitest/issues/10095) [<samp>(a120e)</samp>](https://github.com/vitest-dev/vitest/commit/a120e3ab8)
  - Support aria snapshot  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **Claude Opus 4.6 (1M context)**, [@&#8203;AriPerkkio](https://github.com/AriPerkkio), **Codex** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9668](https://github.com/vitest-dev/vitest/issues/9668) [<samp>(d4fbb)</samp>](https://github.com/vitest-dev/vitest/commit/d4fbb5cc9)
- **reporter**:
  - Add filterMeta option to json reporter  -  by [@&#8203;nami8824](https://github.com/nami8824) and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10078](https://github.com/vitest-dev/vitest/issues/10078) [<samp>(b77de)</samp>](https://github.com/vitest-dev/vitest/commit/b77de968e)

#####    🐞 Bug Fixes

- Use "black" foreground for labeled terminal message to ensure contrast  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;10076](https://github.com/vitest-dev/vitest/issues/10076) [<samp>(203f0)</samp>](https://github.com/vitest-dev/vitest/commit/203f07af7)
- Make `expect(..., message)` consistent as error message prefix  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Codex** in [#&#8203;10068](https://github.com/vitest-dev/vitest/issues/10068) [<samp>(a1b5f)</samp>](https://github.com/vitest-dev/vitest/commit/a1b5f0f4f)
- Do not hoist imports whose names match class properties .  -  by [@&#8203;SunsetFi](https://github.com/SunsetFi) in [#&#8203;10093](https://github.com/vitest-dev/vitest/issues/10093) and [#&#8203;10094](https://github.com/vitest-dev/vitest/issues/10094) [<samp>(0fc4b)</samp>](https://github.com/vitest-dev/vitest/commit/0fc4b47e0)
- **browser**: Spread user server options into browser Vite server in project  -  by [@&#8203;GoldStrikeArch](https://github.com/GoldStrikeArch) in [#&#8203;10049](https://github.com/vitest-dev/vitest/issues/10049) [<samp>(65c9d)</samp>](https://github.com/vitest-dev/vitest/commit/65c9d55eb)

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

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

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

#####    🚀 Experimental Features

- Add `experimental.preParse` flag  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10070](https://github.com/vitest-dev/vitest/issues/10070) [<samp>(78273)</samp>](https://github.com/vitest-dev/vitest/commit/7827363bd)
- Support `browser.locators.exact` option  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10013](https://github.com/vitest-dev/vitest/issues/10013) [<samp>(48799)</samp>](https://github.com/vitest-dev/vitest/commit/487990a19)
- Add `TestAttachment.bodyEncoding`  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9969](https://github.com/vitest-dev/vitest/issues/9969) [<samp>(89ca0)</samp>](https://github.com/vitest-dev/vitest/commit/89ca0e254)
- Support custom snapshot matcher  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **Claude Sonnet 4.6** and **Codex** in [#&#8203;9973](https://github.com/vitest-dev/vitest/issues/9973) [<samp>(59b0e)</samp>](https://github.com/vitest-dev/vitest/commit/59b0e6411)

#####    🐞 Bug Fixes

- Advance fake timers with `expect.poll` interval  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Sonnet 4.6** in [#&#8203;10022](https://github.com/vitest-dev/vitest/issues/10022) [<samp>(3f5bf)</samp>](https://github.com/vitest-dev/vitest/commit/3f5bfa365)
- Add `@vitest/coverage-v8` and `@vitest/coverage-istanbul` as optional dependency  -  by [@&#8203;alan-agius4](https://github.com/alan-agius4) in [#&#8203;10025](https://github.com/vitest-dev/vitest/issues/10025) [<samp>(146d4)</samp>](https://github.com/vitest-dev/vitest/commit/146d4f0a0)
- Fix `defineHelper` for webkit async stack trace + update playwright 1.59.0  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;10036](https://github.com/vitest-dev/vitest/issues/10036) [<samp>(5a5fa)</samp>](https://github.com/vitest-dev/vitest/commit/5a5fa49fe)
- Fix suite hook throwing errors for unused auto test-scoped fixture  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Sonnet 4.6** in [#&#8203;10035](https://github.com/vitest-dev/vitest/issues/10035) [<samp>(39865)</samp>](https://github.com/vitest-dev/vitest/commit/398657e8d)
- **expect**:
  - Remove `JestExtendError.context` from verbose error reporting  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9983](https://github.com/vitest-dev/vitest/issues/9983) [<samp>(66751)</samp>](https://github.com/vitest-dev/vitest/commit/66751c9e8)
  - Don't leak "runner" types  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10004](https://github.com/vitest-dev/vitest/issues/10004) [<samp>(ec204)</samp>](https://github.com/vitest-dev/vitest/commit/ec2045543)
- **snapshot**:
  - Fix flagging obsolete snapshots for snapshot properties mismatch  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Sonnet 4.6** in [#&#8203;9986](https://github.com/vitest-dev/vitest/issues/9986) [<samp>(6b869)</samp>](https://github.com/vitest-dev/vitest/commit/6b869156b)
  - Export custom snapshot matcher helper from `vitest`  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Codex** in [#&#8203;10042](https://github.com/vitest-dev/vitest/issues/10042) [<samp>(691d3)</samp>](https://github.com/vitest-dev/vitest/commit/691d341fd)
- **ui**:
  - Don't leak vite types  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;10005](https://github.com/vitest-dev/vitest/issues/10005) [<samp>(fdff1)</samp>](https://github.com/vitest-dev/vitest/commit/fdff1bf9a)
- **vm**:
  - Fix external module resolve error with deps optimizer query  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Sonnet 4.6** in [#&#8203;10024](https://github.com/vitest-dev/vitest/issues/10024) [<samp>(9dbf4)</samp>](https://github.com/vitest-dev/vitest/commit/9dbf47786)

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

</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 [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDMuMCIsInVwZGF0ZWRJblZlciI6IjQzLjEwMy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJhdXRvbWF0aW9uIiwibnBtIl19-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/77
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-04-19 00:52:59 +02:00
RenovateandPhilipp 56dd6822ff chore(deps): update vitest monorepo to v4.1.2 (#76)
Lint / pre-commit Linting (push) Successful in 25s
Test / Run Tests (push) Successful in 32s
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.1` → `4.1.2`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.1.1/4.1.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.1.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.1.1/4.1.2?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.1.1` → `4.1.2`](https://renovatebot.com/diffs/npm/vitest/4.1.1/4.1.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.1.1/4.1.2?slim=true) |

---

### Release Notes

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

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

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

This release bumps Vitest's `flatted` version and removes version pinning to resolve `flatted`'s CVE related issues ([#&#8203;9975](https://github.com/vitest-dev/vitest/issues/9975)).

#####    🐞 Bug Fixes

- Don't resolve `setupFiles` from parent directory  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9960](https://github.com/vitest-dev/vitest/issues/9960) [<samp>(7aa93)</samp>](https://github.com/vitest-dev/vitest/commit/7aa937776)
- Ensure sequential mock/unmock resolution  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9830](https://github.com/vitest-dev/vitest/issues/9830) [<samp>(7c065)</samp>](https://github.com/vitest-dev/vitest/commit/7c06598db)
- **browser**: Take failure screenshot if `toMatchScreenshot` can't capture a stable screenshot  -  by [@&#8203;macarie](https://github.com/macarie) in [#&#8203;9847](https://github.com/vitest-dev/vitest/issues/9847) [<samp>(faace)</samp>](https://github.com/vitest-dev/vitest/commit/faace1fbe)
- **coverage**: Correct `coverageConfigDefaults` values and types  -  by [@&#8203;Arthie](https://github.com/Arthie) in [#&#8203;9940](https://github.com/vitest-dev/vitest/issues/9940) [<samp>(b3c99)</samp>](https://github.com/vitest-dev/vitest/commit/b3c992cb2)
- **pretty-format**: Fix output limit over counting  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9965](https://github.com/vitest-dev/vitest/issues/9965) [<samp>(d3b7a)</samp>](https://github.com/vitest-dev/vitest/commit/d3b7a40fa)
- Disable colors if agent is detected  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) and [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9851](https://github.com/vitest-dev/vitest/issues/9851) [<samp>(6f97b)</samp>](https://github.com/vitest-dev/vitest/commit/6f97b55dd)

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

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "on monday" (UTC), 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 [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My44Ni4yIiwidXBkYXRlZEluVmVyIjoiNDMuODYuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiYXV0b21hdGlvbiIsIm5wbSJdfQ==-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/76
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-04-06 03:20:15 +02:00
RenovateandPhilipp 6345c28950 chore(deps): update vitest monorepo to v4.1.1 (#75)
Lint / pre-commit Linting (push) Successful in 34s
Test / Run Tests (push) Successful in 38s
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.0.18` → `4.1.1`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.0.18/4.1.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.1.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.0.18/4.1.1?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.0.18` → `4.1.1`](https://renovatebot.com/diffs/npm/vitest/4.0.18/4.1.1) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.1?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.0.18/4.1.1?slim=true) |

---

### Release Notes

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

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

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

#####    🚀 Features

- **experimental**:
  - Expose `matchesTags` to test if the current filter matches tags  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9913](https://github.com/vitest-dev/vitest/issues/9913) [<samp>(eec53)</samp>](https://github.com/vitest-dev/vitest/commit/eec53d9f5)
  - Introduce `experimental.vcsProvider`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9928](https://github.com/vitest-dev/vitest/issues/9928) [<samp>(56115)</samp>](https://github.com/vitest-dev/vitest/commit/561150036)

#####    🐞 Bug Fixes

- Mark `TestProject.testFilesList` internal properly  -  by [@&#8203;sapphi-red](https://github.com/sapphi-red) in [#&#8203;9867](https://github.com/vitest-dev/vitest/issues/9867) [<samp>(54f26)</samp>](https://github.com/vitest-dev/vitest/commit/54f2660f5)
- Detect fixture that returns without calling `use`  -  by [@&#8203;oilater](https://github.com/oilater) in [#&#8203;9831](https://github.com/vitest-dev/vitest/issues/9831) and [#&#8203;9861](https://github.com/vitest-dev/vitest/issues/9861) [<samp>(633ae)</samp>](https://github.com/vitest-dev/vitest/commit/633ae2303)
- Drop vite 8.beta support  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9862](https://github.com/vitest-dev/vitest/issues/9862) [<samp>(b78f5)</samp>](https://github.com/vitest-dev/vitest/commit/b78f5389d)
- Type regression in vi.mocked() static class methods  -  by [@&#8203;purepear](https://github.com/purepear) and [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9857](https://github.com/vitest-dev/vitest/issues/9857) [<samp>(90926)</samp>](https://github.com/vitest-dev/vitest/commit/90926641b)
- Properly re-evaluate actual modules of mocked external  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9898](https://github.com/vitest-dev/vitest/issues/9898) [<samp>(ae5ec)</samp>](https://github.com/vitest-dev/vitest/commit/ae5ec03ef)
- Preserve coverage report when html reporter overlaps  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9889](https://github.com/vitest-dev/vitest/issues/9889) [<samp>(2d81a)</samp>](https://github.com/vitest-dev/vitest/commit/2d81ad897)
- Provide `vi.advanceTimers` to the preview provider  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9891](https://github.com/vitest-dev/vitest/issues/9891) [<samp>(1bc3e)</samp>](https://github.com/vitest-dev/vitest/commit/1bc3e63be)
- Don't leak event listener in playwright provider  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9910](https://github.com/vitest-dev/vitest/issues/9910) [<samp>(d9355)</samp>](https://github.com/vitest-dev/vitest/commit/d93550ff7)
- Open browser in `--standalone` mode without running tests  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9911](https://github.com/vitest-dev/vitest/issues/9911) [<samp>(e78ad)</samp>](https://github.com/vitest-dev/vitest/commit/e78adcf97)
- Guard disposable and optional `body`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9912](https://github.com/vitest-dev/vitest/issues/9912) [<samp>(6fdb2)</samp>](https://github.com/vitest-dev/vitest/commit/6fdb2ba61)
- Resolve `retry.condition` RegExp serialization issue  -  by [@&#8203;nstepien](https://github.com/nstepien) and [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9942](https://github.com/vitest-dev/vitest/issues/9942) [<samp>(7b605)</samp>](https://github.com/vitest-dev/vitest/commit/7b6054328)
- **collect**:
  - Don't treat extra props on `test` return as tests  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9871](https://github.com/vitest-dev/vitest/issues/9871) [<samp>(141e7)</samp>](https://github.com/vitest-dev/vitest/commit/141e72aa1)
- **coverage**:
  - Simplify provider types  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9931](https://github.com/vitest-dev/vitest/issues/9931) [<samp>(aaf9f)</samp>](https://github.com/vitest-dev/vitest/commit/aaf9f18ae)
  - Load built-in provider without module runner  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9939](https://github.com/vitest-dev/vitest/issues/9939) [<samp>(bf892)</samp>](https://github.com/vitest-dev/vitest/commit/bf8920817)
- **expect**:
  - Soft assertions continue after .resolves/.rejects promise errors  -  by [@&#8203;mixelburg](https://github.com/mixelburg), **Maks Pikov**, **Claude Opus 4.6 (1M context)** and [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9843](https://github.com/vitest-dev/vitest/issues/9843) [<samp>(6d74b)</samp>](https://github.com/vitest-dev/vitest/commit/6d74b4948)
  - Fix sinon-chai style API  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9943](https://github.com/vitest-dev/vitest/issues/9943) [<samp>(0f08d)</samp>](https://github.com/vitest-dev/vitest/commit/0f08dda2c)
- **pretty-format**:
  - Limit output for large object  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6 (1M context)** in [#&#8203;9949](https://github.com/vitest-dev/vitest/issues/9949) [<samp>(0d5f9)</samp>](https://github.com/vitest-dev/vitest/commit/0d5f9d6ef)

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

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

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

Vitest 4.1 is out!

This release page lists all changes made to the project during the 4.1 beta. To get a review of all the new features, read our [blog post](https://vitest.dev/blog/vitest-4-1).

#####    🚀 Features

- Return a disposable from doMock()  -  by [@&#8203;kirkwaiblinger](https://github.com/kirkwaiblinger) in [#&#8203;9332](https://github.com/vitest-dev/vitest/issues/9332) [<samp>(e3e65)</samp>](https://github.com/vitest-dev/vitest/commit/e3e659a96)
- Added chai style assertions  -  by [@&#8203;ronnakamoto](https://github.com/ronnakamoto) and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;8842](https://github.com/vitest-dev/vitest/issues/8842) [<samp>(841df)</samp>](https://github.com/vitest-dev/vitest/commit/841df9ac5)
- Update to sinon/fake-timers v15 and add `setTickMode` to timer controls  -  by [@&#8203;atscott](https://github.com/atscott) and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;8726](https://github.com/vitest-dev/vitest/issues/8726) [<samp>(4b480)</samp>](https://github.com/vitest-dev/vitest/commit/4b480aaed)
- Expose matcher types  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9448](https://github.com/vitest-dev/vitest/issues/9448) [<samp>(3e4b9)</samp>](https://github.com/vitest-dev/vitest/commit/3e4b913b1)
- Add `toTestSpecification` to reported tasks  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9464](https://github.com/vitest-dev/vitest/issues/9464) [<samp>(1a470)</samp>](https://github.com/vitest-dev/vitest/commit/1a4705da9)
- Show a warning if `vi.mock` or `vi.hoisted` are declared outside of top level of the module  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9387](https://github.com/vitest-dev/vitest/issues/9387) [<samp>(5db54)</samp>](https://github.com/vitest-dev/vitest/commit/5db54a468)
- Track and display expectedly failed tests (.fails) in UI and CLI  -  by [@&#8203;Copilot](https://github.com/Copilot), **sheremet-va** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9476](https://github.com/vitest-dev/vitest/issues/9476) [<samp>(77d75)</samp>](https://github.com/vitest-dev/vitest/commit/77d75fd34)
- Support tags  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9478](https://github.com/vitest-dev/vitest/issues/9478) [<samp>(de7c8)</samp>](https://github.com/vitest-dev/vitest/commit/de7c8a521)
- Implement `aroundEach` and `aroundAll` hooks  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9450](https://github.com/vitest-dev/vitest/issues/9450) [<samp>(2a8cb)</samp>](https://github.com/vitest-dev/vitest/commit/2a8cb9dc2)
- Stabilize experimental features  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9529](https://github.com/vitest-dev/vitest/issues/9529) [<samp>(b5fd2)</samp>](https://github.com/vitest-dev/vitest/commit/b5fd2a16a)
- Accept `new` or `all` in `--update` flag  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9543](https://github.com/vitest-dev/vitest/issues/9543) [<samp>(a5acf)</samp>](https://github.com/vitest-dev/vitest/commit/a5acf28a5)
- Support `meta` in test options  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9535](https://github.com/vitest-dev/vitest/issues/9535) [<samp>(7d622)</samp>](https://github.com/vitest-dev/vitest/commit/7d622e3d1)
- Support type inference with a new `test.extend` syntax  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9550](https://github.com/vitest-dev/vitest/issues/9550) [<samp>(e5385)</samp>](https://github.com/vitest-dev/vitest/commit/e53854fcc)
- Support vite 8 beta, fix type issues in the config with different vite versions  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9587](https://github.com/vitest-dev/vitest/issues/9587) [<samp>(99028)</samp>](https://github.com/vitest-dev/vitest/commit/990281dfd)
- Add assertion helper to hide internal stack traces  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9594](https://github.com/vitest-dev/vitest/issues/9594) [<samp>(eeb0a)</samp>](https://github.com/vitest-dev/vitest/commit/eeb0ae2f8)
- Store failure screenshots using artifacts API  -  by [@&#8203;macarie](https://github.com/macarie) in [#&#8203;9588](https://github.com/vitest-dev/vitest/issues/9588) [<samp>(24603)</samp>](https://github.com/vitest-dev/vitest/commit/24603e3c4)
- Allow `vitest list` to statically collect tests instead of running files to collect them  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9630](https://github.com/vitest-dev/vitest/issues/9630) [<samp>(7a8e7)</samp>](https://github.com/vitest-dev/vitest/commit/7a8e7fc20)
- Add `--detect-async-leaks`  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9528](https://github.com/vitest-dev/vitest/issues/9528) [<samp>(c594d)</samp>](https://github.com/vitest-dev/vitest/commit/c594d4af3)
- Implement `mockThrow` and `mockThrowOnce`  -  by [@&#8203;thor-juhasz](https://github.com/thor-juhasz) and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9512](https://github.com/vitest-dev/vitest/issues/9512) [<samp>(61917)</samp>](https://github.com/vitest-dev/vitest/commit/619179fb7)
- Support `update: "none"` and add docs about snapshots behavior on CI  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9700](https://github.com/vitest-dev/vitest/issues/9700) [<samp>(05f18)</samp>](https://github.com/vitest-dev/vitest/commit/05f1854e2)
- Support playwright `launchOptions` with `connectOptions`  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9702](https://github.com/vitest-dev/vitest/issues/9702) [<samp>(f0ff1)</samp>](https://github.com/vitest-dev/vitest/commit/f0ff1b2a0)
- Add `page/locator.mark` API to enhance playwright trace  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9652](https://github.com/vitest-dev/vitest/issues/9652) [<samp>(d0ee5)</samp>](https://github.com/vitest-dev/vitest/commit/d0ee546fe)
- **api**:
  - Support tests starting or ending with `test` in `experimental_parseSpecification`  -  by [@&#8203;jgillick](https://github.com/jgillick) and **Jeremy Gillick** in [#&#8203;9235](https://github.com/vitest-dev/vitest/issues/9235) [<samp>(2f367)</samp>](https://github.com/vitest-dev/vitest/commit/2f367fad3)
  - Add filters to `createSpecification`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9336](https://github.com/vitest-dev/vitest/issues/9336) [<samp>(c8e6c)</samp>](https://github.com/vitest-dev/vitest/commit/c8e6c7fbf)
  - Expose `runTestFiles` as alternative to `runTestSpecifications`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9443](https://github.com/vitest-dev/vitest/issues/9443) [<samp>(43d76)</samp>](https://github.com/vitest-dev/vitest/commit/43d761821)
  - Add `allowWrite` and `allowExec` options to `api`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9350](https://github.com/vitest-dev/vitest/issues/9350) [<samp>(20e00)</samp>](https://github.com/vitest-dev/vitest/commit/20e00ef78)
  - Allow passing down test cases to `toTestSpecification`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9627](https://github.com/vitest-dev/vitest/issues/9627) [<samp>(6f17d)</samp>](https://github.com/vitest-dev/vitest/commit/6f17d5ddf)
- **browser**:
  - Add `userEvent.wheel` API  -  by [@&#8203;macarie](https://github.com/macarie) in [#&#8203;9188](https://github.com/vitest-dev/vitest/issues/9188) [<samp>(66080)</samp>](https://github.com/vitest-dev/vitest/commit/660801979)
  - Add `filterNode` option to prettyDOM for filtering browser assertion error output  -  by [@&#8203;Copilot](https://github.com/Copilot), **sheremet-va** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9475](https://github.com/vitest-dev/vitest/issues/9475) [<samp>(d3220)</samp>](https://github.com/vitest-dev/vitest/commit/d3220fcd8)
  - Support playwright persistent context  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **Claude Opus 4.6** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9229](https://github.com/vitest-dev/vitest/issues/9229) [<samp>(f865d)</samp>](https://github.com/vitest-dev/vitest/commit/f865d2ba4)
  - Added `detailsPanelPosition` option and button  -  by [@&#8203;shairez](https://github.com/shairez) in [#&#8203;9525](https://github.com/vitest-dev/vitest/issues/9525) [<samp>(c8a31)</samp>](https://github.com/vitest-dev/vitest/commit/c8a31147c)
  - Use BlazeDiff instead of pixelmatch  -  by [@&#8203;macarie](https://github.com/macarie) in [#&#8203;9514](https://github.com/vitest-dev/vitest/issues/9514) [<samp>(30936)</samp>](https://github.com/vitest-dev/vitest/commit/309362089)
  - Add `findElement` and enable strict mode in webdriverio and preview  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9677](https://github.com/vitest-dev/vitest/issues/9677) [<samp>(c3f37)</samp>](https://github.com/vitest-dev/vitest/commit/c3f37721c)
- **cli**:
  - Add [@&#8203;bomb](https://github.com/bomb).sh/tab completions  -  by [@&#8203;AmirSa12](https://github.com/AmirSa12) and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;8639](https://github.com/vitest-dev/vitest/issues/8639) [<samp>(200f3)</samp>](https://github.com/vitest-dev/vitest/commit/200f31704)
- **coverage**:
  - Support `ignore start/stop` ignore hints  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9204](https://github.com/vitest-dev/vitest/issues/9204) [<samp>(e59c9)</samp>](https://github.com/vitest-dev/vitest/commit/e59c94ba6)
  - Add `coverage.changed` option to report only changed files  -  by [@&#8203;kykim00](https://github.com/kykim00) and [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9521](https://github.com/vitest-dev/vitest/issues/9521) [<samp>(1d939)</samp>](https://github.com/vitest-dev/vitest/commit/1d9392c67)
- **experimental**:
  - Add `onModuleRunner` hook to `worker.init`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9286](https://github.com/vitest-dev/vitest/issues/9286) [<samp>(e977f)</samp>](https://github.com/vitest-dev/vitest/commit/e977f3deb)
  - Option to disable the module runner  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) and [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9210](https://github.com/vitest-dev/vitest/issues/9210) [<samp>(9be61)</samp>](https://github.com/vitest-dev/vitest/commit/9be6121ee)
  - Add `importDurations: { limit, print }` options  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **Claude Opus 4.6** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9401](https://github.com/vitest-dev/vitest/issues/9401) [<samp>(7e10f)</samp>](https://github.com/vitest-dev/vitest/commit/7e10fb356)
  - Add print and fail thresholds for `importDurations`  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9533](https://github.com/vitest-dev/vitest/issues/9533) [<samp>(3f7a5)</samp>](https://github.com/vitest-dev/vitest/commit/3f7a5f8f8)
- **fixtures**:
  - Pass down file context to `beforeAll`/`afterAll`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9572](https://github.com/vitest-dev/vitest/issues/9572) [<samp>(c8339)</samp>](https://github.com/vitest-dev/vitest/commit/c83395f2c)
- **reporters**:
  - Add `agent` reporter to reduce ai agent token usage  -  by [@&#8203;cpojer](https://github.com/cpojer) in [#&#8203;9779](https://github.com/vitest-dev/vitest/issues/9779) [<samp>(3e9e0)</samp>](https://github.com/vitest-dev/vitest/commit/3e9e096a2)
- **runner**:
  - Enhance `retry` options  -  by [@&#8203;MazenSamehR](https://github.com/MazenSamehR), **Matan Shavit**, [@&#8203;AriPerkkio](https://github.com/AriPerkkio) and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9370](https://github.com/vitest-dev/vitest/issues/9370) [<samp>(9e4cf)</samp>](https://github.com/vitest-dev/vitest/commit/9e4cfd295)
- **ui**:
  - Allow run individual test/suites  -  by [@&#8203;userquin](https://github.com/userquin) in [#&#8203;9465](https://github.com/vitest-dev/vitest/issues/9465) [<samp>(73b10)</samp>](https://github.com/vitest-dev/vitest/commit/73b10f1b9)
  - Add project filter/sort support  -  by [@&#8203;userquin](https://github.com/userquin) in [#&#8203;8689](https://github.com/vitest-dev/vitest/issues/8689) [<samp>(0c7ea)</samp>](https://github.com/vitest-dev/vitest/commit/0c7eaac16)
  - Add duration sorting to explorer  -  by [@&#8203;julianhahn](https://github.com/julianhahn) and [@&#8203;cursoragent](https://github.com/cursoragent) in [#&#8203;9603](https://github.com/vitest-dev/vitest/issues/9603) [<samp>(209b1)</samp>](https://github.com/vitest-dev/vitest/commit/209b1b0e1)
  - Implement filter for slow tests  -  by [@&#8203;DerYeger](https://github.com/DerYeger) and [@&#8203;userquin](https://github.com/userquin) in [#&#8203;9705](https://github.com/vitest-dev/vitest/issues/9705) [<samp>(8880c)</samp>](https://github.com/vitest-dev/vitest/commit/8880c907a)
- **vitest**:
  - Add run summary in GitHub Actions Reporter  -  by [@&#8203;macarie](https://github.com/macarie) and **jhnance** in [#&#8203;9579](https://github.com/vitest-dev/vitest/issues/9579) [<samp>(96bfc)</samp>](https://github.com/vitest-dev/vitest/commit/96bfc8345)

#####    🐞 Bug Fixes

- Deprecate several vitest/\* entry points  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9347](https://github.com/vitest-dev/vitest/issues/9347) [<samp>(fd459)</samp>](https://github.com/vitest-dev/vitest/commit/fd45928be)
- Use `meta.url` in `createRequire`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9441](https://github.com/vitest-dev/vitest/issues/9441) [<samp>(e3422)</samp>](https://github.com/vitest-dev/vitest/commit/e34225563)
- Preact browser mode init example of render function not async  -  by [@&#8203;WuMingDao](https://github.com/WuMingDao) in [#&#8203;9375](https://github.com/vitest-dev/vitest/issues/9375) [<samp>(2bea5)</samp>](https://github.com/vitest-dev/vitest/commit/2bea549c7)
- Deprecate unused types in matcher context  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9449](https://github.com/vitest-dev/vitest/issues/9449) [<samp>(20f87)</samp>](https://github.com/vitest-dev/vitest/commit/20f8753a2)
- Handle `external/noExternal` during `configEnvironment` hook  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9508](https://github.com/vitest-dev/vitest/issues/9508) [<samp>(59ea2)</samp>](https://github.com/vitest-dev/vitest/commit/59ea27c1c)
- Replace default ssr environment runner with Vitest server module runner  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9506](https://github.com/vitest-dev/vitest/issues/9506) [<samp>(cd5db)</samp>](https://github.com/vitest-dev/vitest/commit/cd5db660c)
- Propagate experimental CLI options to child projects  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9531](https://github.com/vitest-dev/vitest/issues/9531) [<samp>(b624f)</samp>](https://github.com/vitest-dev/vitest/commit/b624fae53)
- Show a warning when `browser.isolate` is used  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9410](https://github.com/vitest-dev/vitest/issues/9410) [<samp>(3d48e)</samp>](https://github.com/vitest-dev/vitest/commit/3d48ebcb9)
- Fix `vi.mock({ spy: true })` node v8 coverage  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **hi-ogawa** and **Claude Opus 4.6** in [#&#8203;9541](https://github.com/vitest-dev/vitest/issues/9541) [<samp>(687b6)</samp>](https://github.com/vitest-dev/vitest/commit/687b633c1)
- Don't show internal ssr handler in errors  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9547](https://github.com/vitest-dev/vitest/issues/9547) [<samp>(76c43)</samp>](https://github.com/vitest-dev/vitest/commit/76c4397b5)
- Close vitest if it failed to start  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9573](https://github.com/vitest-dev/vitest/issues/9573) [<samp>(728ba)</samp>](https://github.com/vitest-dev/vitest/commit/728ba617f)
- Fix ssr environment runner in project  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9584](https://github.com/vitest-dev/vitest/issues/9584) [<samp>(09006)</samp>](https://github.com/vitest-dev/vitest/commit/090064f97)
- Trim trailing white spaces in code block  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9591](https://github.com/vitest-dev/vitest/issues/9591) [<samp>(f78be)</samp>](https://github.com/vitest-dev/vitest/commit/f78bea992)
- Support inline snapshot inside test.for/each  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9590](https://github.com/vitest-dev/vitest/issues/9590) [<samp>(615fd)</samp>](https://github.com/vitest-dev/vitest/commit/615fd521e)
- Apply source maps for external module stack trace  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9152](https://github.com/vitest-dev/vitest/issues/9152) [<samp>(79e20)</samp>](https://github.com/vitest-dev/vitest/commit/79e20d5a3)
- Remove the `.name` from statically collected test  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9596](https://github.com/vitest-dev/vitest/issues/9596) [<samp>(b66ff)</samp>](https://github.com/vitest-dev/vitest/commit/b66ff691a)
- Don't suppress warnings on pnp  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9602](https://github.com/vitest-dev/vitest/issues/9602) [<samp>(89cbd)</samp>](https://github.com/vitest-dev/vitest/commit/89cbdaea3)
- Support snapshot with `expect.soft`  -  by [@&#8203;iumehara](https://github.com/iumehara), [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9231](https://github.com/vitest-dev/vitest/issues/9231) [<samp>(3eb2c)</samp>](https://github.com/vitest-dev/vitest/commit/3eb2cd541)
- Log seed when only `sequence.shuffle.tests` is enabled  -  by [@&#8203;kaigritun](https://github.com/kaigritun), **Kai Gritun** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9576](https://github.com/vitest-dev/vitest/issues/9576) [<samp>(8182b)</samp>](https://github.com/vitest-dev/vitest/commit/8182b77ad)
- Externalize `expect/src/utils` from `vitest`  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9616](https://github.com/vitest-dev/vitest/issues/9616) [<samp>(48739)</samp>](https://github.com/vitest-dev/vitest/commit/487398422)
- Ignore test.override during static collection  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9620](https://github.com/vitest-dev/vitest/issues/9620) [<samp>(09174)</samp>](https://github.com/vitest-dev/vitest/commit/0917470ce)
- Increase stacktrace limit for `--detect-async-leaks`  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9638](https://github.com/vitest-dev/vitest/issues/9638) [<samp>(9fd4c)</samp>](https://github.com/vitest-dev/vitest/commit/9fd4ce533)
- Hanging-reporter link in cli  -  by [@&#8203;flx-sta](https://github.com/flx-sta) in [#&#8203;9649](https://github.com/vitest-dev/vitest/issues/9649) [<samp>(7c103)</samp>](https://github.com/vitest-dev/vitest/commit/7c103055c)
- Fix teardown timeout of `aroundEach/All` when inner `aroundEach/All` throws  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9657](https://github.com/vitest-dev/vitest/issues/9657) [<samp>(4ec6c)</samp>](https://github.com/vitest-dev/vitest/commit/4ec6cb305)
- Fix ui mode / html reporter and coverage integration  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9626](https://github.com/vitest-dev/vitest/issues/9626) [<samp>(86fad)</samp>](https://github.com/vitest-dev/vitest/commit/86fad4b42)
- Don't continue when `aroundEach/All` setup timed out  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9670](https://github.com/vitest-dev/vitest/issues/9670) [<samp>(bb013)</samp>](https://github.com/vitest-dev/vitest/commit/bb013d54b)
- Align `VitestRunnerConfig` optional fields with `SerializedConfig`  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9661](https://github.com/vitest-dev/vitest/issues/9661) [<samp>(79520)</samp>](https://github.com/vitest-dev/vitest/commit/79520d82d)
- Handle Symbol values in format utility  -  by [@&#8203;nami8824](https://github.com/nami8824) in [#&#8203;9658](https://github.com/vitest-dev/vitest/issues/9658) [<samp>(0583f)</samp>](https://github.com/vitest-dev/vitest/commit/0583f067e)
- Deprecate `toBe*` spy assertions in favor of `toHaveBeen*` (and `toThrowError`)  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9665](https://github.com/vitest-dev/vitest/issues/9665) [<samp>(4d390)</samp>](https://github.com/vitest-dev/vitest/commit/4d390dfe9)
- Don't propagate nested `aroundEach/All` errors but aggregate them on runner  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9673](https://github.com/vitest-dev/vitest/issues/9673) [<samp>(b6365)</samp>](https://github.com/vitest-dev/vitest/commit/b63653f5a)
- Show a better error if there is a pending dynamic import  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9676](https://github.com/vitest-dev/vitest/issues/9676) [<samp>(7ef5c)</samp>](https://github.com/vitest-dev/vitest/commit/7ef5cf4b7)
- Preserve stack trace of `resolves/rejects` chained assertion error  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9679](https://github.com/vitest-dev/vitest/issues/9679) [<samp>(c6151)</samp>](https://github.com/vitest-dev/vitest/commit/c61511d4a)
- Handle module-sync condition in vmThreads/vmForks require  -  by [@&#8203;lesleh](https://github.com/lesleh) in [#&#8203;9650](https://github.com/vitest-dev/vitest/issues/9650) and [#&#8203;9651](https://github.com/vitest-dev/vitest/issues/9651) [<samp>(bb203)</samp>](https://github.com/vitest-dev/vitest/commit/bb20389f4)
- Hooks should respect `maxConcurrency`  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9653](https://github.com/vitest-dev/vitest/issues/9653) [<samp>(16d13)</samp>](https://github.com/vitest-dev/vitest/commit/16d13d981)
- Recursively autospy module object  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9687](https://github.com/vitest-dev/vitest/issues/9687) [<samp>(695a8)</samp>](https://github.com/vitest-dev/vitest/commit/695a86b41)
- Remove trailing spaces from diff error log  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9680](https://github.com/vitest-dev/vitest/issues/9680) [<samp>(395d1)</samp>](https://github.com/vitest-dev/vitest/commit/395d1a29e)
- Respect project `resolve.conditions` for externals  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9717](https://github.com/vitest-dev/vitest/issues/9717) [<samp>(1d498)</samp>](https://github.com/vitest-dev/vitest/commit/1d4987498)
- Use object for WeakMap instead of a symbol to support webcontainers  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9731](https://github.com/vitest-dev/vitest/issues/9731) [<samp>(c5225)</samp>](https://github.com/vitest-dev/vitest/commit/c52259330)
- Fix re-mocking virtual module  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9748](https://github.com/vitest-dev/vitest/issues/9748) [<samp>(3cbbb)</samp>](https://github.com/vitest-dev/vitest/commit/3cbbb17f1)
- Cancelling should stop current test immediately  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9729](https://github.com/vitest-dev/vitest/issues/9729) [<samp>(0cb2f)</samp>](https://github.com/vitest-dev/vitest/commit/0cb2f7239)
- Make `mockObject` change backwards compatible  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9744](https://github.com/vitest-dev/vitest/issues/9744) [<samp>(84c69)</samp>](https://github.com/vitest-dev/vitest/commit/84c69497f)
- Fix `URL.name` on jsdom  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9767](https://github.com/vitest-dev/vitest/issues/9767) [<samp>(031f3)</samp>](https://github.com/vitest-dev/vitest/commit/031f3a374)
- Save and restore module graph in blob reporter  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9740](https://github.com/vitest-dev/vitest/issues/9740) [<samp>(84355)</samp>](https://github.com/vitest-dev/vitest/commit/843554bf0)
- Don't silence reporter errors from test runtime events handler in normal run and --merge-reports  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9727](https://github.com/vitest-dev/vitest/issues/9727) [<samp>(4072d)</samp>](https://github.com/vitest-dev/vitest/commit/4072d0132)
- Fix `vi.importActual()` for virtual modules  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9772](https://github.com/vitest-dev/vitest/issues/9772) [<samp>(1e89e)</samp>](https://github.com/vitest-dev/vitest/commit/1e89ec020)
- Throw `FixtureAccessError` if suite hook accesses undefined fixture  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9786](https://github.com/vitest-dev/vitest/issues/9786) [<samp>(fc2ce)</samp>](https://github.com/vitest-dev/vitest/commit/fc2cea2b7)
- Allow hyphens in project config file name pattern  -  by [@&#8203;Koutaro-Hanabusa](https://github.com/Koutaro-Hanabusa) and [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9760](https://github.com/vitest-dev/vitest/issues/9760) [<samp>(33e96)</samp>](https://github.com/vitest-dev/vitest/commit/33e96311a)
- Manual and redirect mock shouldn't `load` or `transform` original module  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9774](https://github.com/vitest-dev/vitest/issues/9774) [<samp>(a8216)</samp>](https://github.com/vitest-dev/vitest/commit/a8216b001)
- `hideSkippedTests` should not hide `test.todo`  -  by [@&#8203;oilater](https://github.com/oilater) in [#&#8203;9562](https://github.com/vitest-dev/vitest/issues/9562) and [#&#8203;9781](https://github.com/vitest-dev/vitest/issues/9781) [<samp>(8181e)</samp>](https://github.com/vitest-dev/vitest/commit/8181e06e7)
- Allow catch/finally for async assertion  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9827](https://github.com/vitest-dev/vitest/issues/9827) [<samp>(031f0)</samp>](https://github.com/vitest-dev/vitest/commit/031f02a89)
- Resolve fixture overrides from test's suite in `beforeEach` hooks  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9826](https://github.com/vitest-dev/vitest/issues/9826) [<samp>(99e52)</samp>](https://github.com/vitest-dev/vitest/commit/99e52fe58)
- Use isAgent check, not just TTY, for watch mode  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9841](https://github.com/vitest-dev/vitest/issues/9841) [<samp>(c3cac)</samp>](https://github.com/vitest-dev/vitest/commit/c3cac1c1b)
- Use `performance.now` to measure test timeout duration  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9795](https://github.com/vitest-dev/vitest/issues/9795) [<samp>(f48a6)</samp>](https://github.com/vitest-dev/vitest/commit/f48a60114)
- Correctly identify concurrent test during static analysis  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9846](https://github.com/vitest-dev/vitest/issues/9846) [<samp>(1de0a)</samp>](https://github.com/vitest-dev/vitest/commit/1de0aa22d)
- **browser**:
  - Avoid updating screenshots when `toMatchScreenshot` passes  -  by [@&#8203;macarie](https://github.com/macarie) in [#&#8203;9289](https://github.com/vitest-dev/vitest/issues/9289) [<samp>(46aab)</samp>](https://github.com/vitest-dev/vitest/commit/46aabaa44)
  - Hide injected data-testid attributes  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9503](https://github.com/vitest-dev/vitest/issues/9503) [<samp>(c8d2c)</samp>](https://github.com/vitest-dev/vitest/commit/c8d2c411c)
  - Throw an error if iframe was reloaded  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9516](https://github.com/vitest-dev/vitest/issues/9516) [<samp>(73a81)</samp>](https://github.com/vitest-dev/vitest/commit/73a81f880)
  - Encode projectName in browser client URL  -  by [@&#8203;dkkim0122](https://github.com/dkkim0122) in [#&#8203;9523](https://github.com/vitest-dev/vitest/issues/9523) [<samp>(5b164)</samp>](https://github.com/vitest-dev/vitest/commit/5b16483c3)
  - Don't take failure screenshot if tests have artifacts created by `toMatchScreenshot`  -  by [@&#8203;macarie](https://github.com/macarie) in [#&#8203;9552](https://github.com/vitest-dev/vitest/issues/9552) [<samp>(83ca0)</samp>](https://github.com/vitest-dev/vitest/commit/83ca02547)
  - Remove `--remote-debugging-address` from chrome args  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9712](https://github.com/vitest-dev/vitest/issues/9712) [<samp>(f09bb)</samp>](https://github.com/vitest-dev/vitest/commit/f09bb5c32)
  - Make sure userEvent actions support `ensureAwaited`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9732](https://github.com/vitest-dev/vitest/issues/9732) [<samp>(97685)</samp>](https://github.com/vitest-dev/vitest/commit/9768517b8)
  - Types of `getCDPSession` and `cdp()`  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9716](https://github.com/vitest-dev/vitest/issues/9716) [<samp>(689a2)</samp>](https://github.com/vitest-dev/vitest/commit/689a22a1b)
  - Skip esbuild.legalComments when using rolldown-vite  -  by [@&#8203;Copilot](https://github.com/Copilot), **hi-ogawa** and [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9803](https://github.com/vitest-dev/vitest/issues/9803) [<samp>(3505f)</samp>](https://github.com/vitest-dev/vitest/commit/3505fa5a3)
- **chai**:
  - Don't allow `deepEqual` in the config because it's not serializable  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9666](https://github.com/vitest-dev/vitest/issues/9666) [<samp>(9ee99)</samp>](https://github.com/vitest-dev/vitest/commit/9ee999d73)
- **coverage**:
  - Infer transform mode for uncovered files  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9435](https://github.com/vitest-dev/vitest/issues/9435) [<samp>(f3967)</samp>](https://github.com/vitest-dev/vitest/commit/f396792d6)
  - `thresholds.autoUpdate` to preserve ending whitespace  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9436](https://github.com/vitest-dev/vitest/issues/9436) [<samp>(7e534)</samp>](https://github.com/vitest-dev/vitest/commit/7e534a0b6)
- **deps**:
  - Update all non-major dependencies  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9192](https://github.com/vitest-dev/vitest/issues/9192) [<samp>(90c30)</samp>](https://github.com/vitest-dev/vitest/commit/90c302f3b)
  - Update all non-major dependencies  -  in [#&#8203;9485](https://github.com/vitest-dev/vitest/issues/9485) [<samp>(c0118)</samp>](https://github.com/vitest-dev/vitest/commit/c01186022)
  - Update all non-major dependencies  -  in [#&#8203;9567](https://github.com/vitest-dev/vitest/issues/9567) [<samp>(13c9e)</samp>](https://github.com/vitest-dev/vitest/commit/13c9e022b)
- **docs**:
  - Fix old `/config/#option` hash links causing hydration errors  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa), **Claude Opus 4.6** and [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9610](https://github.com/vitest-dev/vitest/issues/9610) [<samp>(a603c)</samp>](https://github.com/vitest-dev/vitest/commit/a603c3a30)
- **expect**:
  - `toMatchObject(Map/Set)` should expect `Map/Set` on left hand side  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9532](https://github.com/vitest-dev/vitest/issues/9532) [<samp>(381da)</samp>](https://github.com/vitest-dev/vitest/commit/381da4a9d)
  - Fix objectContaining with proxy  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9554](https://github.com/vitest-dev/vitest/issues/9554) [<samp>(7ce34)</samp>](https://github.com/vitest-dev/vitest/commit/7ce3417b1)
  - Support arbitrary value equality for `toThrow` and make Error detection robust  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9570](https://github.com/vitest-dev/vitest/issues/9570) [<samp>(de215)</samp>](https://github.com/vitest-dev/vitest/commit/de215c19c)
- **mock**:
  - Inject helpers after hashbang if present  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9545](https://github.com/vitest-dev/vitest/issues/9545) [<samp>(65432)</samp>](https://github.com/vitest-dev/vitest/commit/65432a74b)
- **mocker**:
  - Update vite's peer dependency range  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9808](https://github.com/vitest-dev/vitest/issues/9808) [<samp>(36f9a)</samp>](https://github.com/vitest-dev/vitest/commit/36f9a81a2)
- **reporter**:
  - `dot` reporter leaves pending tests  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9684](https://github.com/vitest-dev/vitest/issues/9684) [<samp>(4d793)</samp>](https://github.com/vitest-dev/vitest/commit/4d7938a56)
- **runner**:
  - Mark repeated tests as finished on last run  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9707](https://github.com/vitest-dev/vitest/issues/9707) [<samp>(cc735)</samp>](https://github.com/vitest-dev/vitest/commit/cc735970a)
- **spy**:
  - Support deep partial in vi.mocked  -  by [@&#8203;j2h30728](https://github.com/j2h30728) in [#&#8203;8152](https://github.com/vitest-dev/vitest/issues/8152) and [#&#8203;9493](https://github.com/vitest-dev/vitest/issues/9493) [<samp>(71cb5)</samp>](https://github.com/vitest-dev/vitest/commit/71cb51ffc)
  - Fallback to object accessor if descriptor's value is `undefined`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9511](https://github.com/vitest-dev/vitest/issues/9511) [<samp>(6f181)</samp>](https://github.com/vitest-dev/vitest/commit/6f18103fa)
  - Throw correct errors when shorthand methods are used on a class  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9513](https://github.com/vitest-dev/vitest/issues/9513) [<samp>(5d0fd)</samp>](https://github.com/vitest-dev/vitest/commit/5d0fd3b62)
- **types**:
  - `bench.reporters` no longer gives type errors when passing file name string paths  -  by [@&#8203;Bertie690](https://github.com/Bertie690) in [#&#8203;9695](https://github.com/vitest-dev/vitest/issues/9695) [<samp>(093c8)</samp>](https://github.com/vitest-dev/vitest/commit/093c8f6b5)
- **ui**:
  - Process artifact attachments when generating HTML reporter  -  by [@&#8203;macarie](https://github.com/macarie) in [#&#8203;9472](https://github.com/vitest-dev/vitest/issues/9472) [<samp>(96eb9)</samp>](https://github.com/vitest-dev/vitest/commit/96eb92826)
  - Don't fail if --ui and --root are specified together  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9536](https://github.com/vitest-dev/vitest/issues/9536) [<samp>(d9305)</samp>](https://github.com/vitest-dev/vitest/commit/d93055fc7)

#####    🏎 Performance

- **pretty-format**: Combine DOMElement plugins  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9581](https://github.com/vitest-dev/vitest/issues/9581) [<samp>(da85a)</samp>](https://github.com/vitest-dev/vitest/commit/da85a3267)

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

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "on monday" (UTC), 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 [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My40Ni42IiwidXBkYXRlZEluVmVyIjoiNDMuODUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiYXV0b21hdGlvbiIsIm5wbSJdfQ==-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/75
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-03-31 01:46:57 +02:00
RenovateandRenovate 6fc5d6253f chore(deps): update pre-commit hook python-jsonschema/check-jsonschema to v0.37.1 (#74)
Lint / pre-commit Linting (push) Successful in 24s
Test / Run Tests (push) Successful in 34s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-03-27 01:35:34 +01:00
RenovateandRenovate f96bfc2f7b chore(deps): pin dependencies (#73)
Lint / pre-commit Linting (push) Successful in 27s
Test / Run Tests (push) Successful in 2m48s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-03-20 23:42:26 +01:00
RenovateandRenovate 34cc58529b chore(deps): update pre-commit hook python-jsonschema/check-jsonschema to v0.37.0 (#74)
Lint / pre-commit Linting (push) Successful in 22s
Test / Run Tests (push) Successful in 3m1s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-02-27 07:04:15 +01:00
Philipp 2be5b2a1bc chore: update @actions/core to v3 and migrate to ESM (#73)
This PR updates the `@actions/core` dependency from v2 to v3.0.0.

### Major Changes:
- **Dependency Update**: Updated `@actions/core` to `v3.0.0` in `package.json`.
- **ESM Migration**: Converted the entire project to ECMAScript Modules (ESM). This was necessary because `@actions/core` v3 is an ESM-only package and does not provide CommonJS exports.
- **`package.json` updates**: Added `"type": "module"` and updated the dependency version.
- **Code Refactor**:
    -   Converted `index.js` to use `import` and `export`.
    -   Updated the main execution check to use `import.meta.url` for ESM compatibility.
    -   Converted `__tests__/index.test.js` to use ESM imports for compatibility with the updated source code.
- **Production Build**: Re-generated the `dist/index.js` bundle using `ncc` to reflect the changes.
- **Node.js Version**: The project continues to use Node 20 (`node20` in `action.yaml`), which is fully compatible with these changes.

### Why these changes are needed:
- `@actions/core` v3 brings latest improvements and fixes from the GitHub Actions toolkit.
- Migrating to ESM is the modern standard for Node.js development and is required to consume ESM-only packages like the new `@actions/core` v3.

Verified with `npm test` and `npm run build`.

Reviewed-on: #73
2026-02-23 03:59:12 +01:00
RenovateandPhilipp cbbd0e64e8 chore(deps): update dependency glob to v13.0.6 (#72)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [glob](https://github.com/isaacs/node-glob) | [`13.0.5` → `13.0.6`](https://renovatebot.com/diffs/npm/glob/13.0.5/13.0.6) | ![age](https://developer.mend.io/api/mc/badges/age/npm/glob/13.0.6?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/glob/13.0.5/13.0.6?slim=true) |

---

### Release Notes

<details>
<summary>isaacs/node-glob (glob)</summary>

### [`v13.0.6`](https://github.com/isaacs/node-glob/compare/v13.0.5...v13.0.6)

[Compare Source](https://github.com/isaacs/node-glob/compare/v13.0.5...v13.0.6)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #72
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-02-22 21:39:43 +01:00
RenovateandRenovate dee3bff2ed chore(deps): update pre-commit hook python-jsonschema/check-jsonschema to v0.36.2 (#70)
Lint / pre-commit Linting (push) Successful in 19s
Test / Run Tests (push) Successful in 20s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-02-18 20:05:29 +01:00
RenovateandPhilipp 2a1480e46b chore(deps): update dependency glob to v13.0.5 (#71)
Lint / pre-commit Linting (push) Successful in 31s
Test / Run Tests (push) Successful in 20s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [glob](https://github.com/isaacs/node-glob) | [`13.0.3` → `13.0.5`](https://renovatebot.com/diffs/npm/glob/13.0.3/13.0.5) | ![age](https://developer.mend.io/api/mc/badges/age/npm/glob/13.0.5?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/glob/13.0.3/13.0.5?slim=true) |

---

### Release Notes

<details>
<summary>isaacs/node-glob (glob)</summary>

### [`v13.0.5`](https://github.com/isaacs/node-glob/compare/v13.0.4...v13.0.5)

[Compare Source](https://github.com/isaacs/node-glob/compare/v13.0.4...v13.0.5)

### [`v13.0.4`](https://github.com/isaacs/node-glob/compare/v13.0.3...v13.0.4)

[Compare Source](https://github.com/isaacs/node-glob/compare/v13.0.3...v13.0.4)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #71
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-02-18 20:03:18 +01:00
RenovateandRenovate 150f8fc15f chore(deps): update pre-commit hook python-jsonschema/check-jsonschema to v0.36.1 (#66)
Lint / pre-commit Linting (push) Successful in 1m9s
Test / Run Tests (push) Successful in 1m23s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-02-13 18:50:21 +01:00
RenovateandPhilipp 96f131369e chore(deps): update dependency glob to v13.0.3 (#68)
Lint / pre-commit Linting (push) Successful in 1m10s
Test / Run Tests (push) Successful in 1m14s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [glob](https://github.com/isaacs/node-glob) | [`13.0.0` → `13.0.3`](https://renovatebot.com/diffs/npm/glob/13.0.0/13.0.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/glob/13.0.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/glob/13.0.0/13.0.3?slim=true) |

---

### Release Notes

<details>
<summary>isaacs/node-glob (glob)</summary>

### [`v13.0.3`](https://github.com/isaacs/node-glob/compare/v13.0.2...782e3a74b42d8f71deca7b4b74cea17a600e59d2)

[Compare Source](https://github.com/isaacs/node-glob/compare/v13.0.2...v13.0.3)

### [`v13.0.2`](https://github.com/isaacs/node-glob/compare/v13.0.1...2135b0c3580caf6330e28dedb2d57cea75f15154)

[Compare Source](https://github.com/isaacs/node-glob/compare/v13.0.1...v13.0.2)

### [`v13.0.1`](https://github.com/isaacs/node-glob/compare/v13.0.0...v13.0.1)

[Compare Source](https://github.com/isaacs/node-glob/compare/v13.0.0...v13.0.1)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #68
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-02-13 18:36:06 +01:00
RenovateandPhilipp 684cc88afb chore(deps): update dependency @actions/core to v2.0.3 (#67)
Test / Run Tests (push) Successful in 59s
Lint / pre-commit Linting (push) Failing after 12m5s
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@actions/core](https://github.com/actions/toolkit/tree/main/packages/core) ([source](https://github.com/actions/toolkit/tree/HEAD/packages/core)) | [`2.0.2` → `2.0.3`](https://renovatebot.com/diffs/npm/@actions%2fcore/2.0.2/2.0.3) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@actions%2fcore/2.0.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@actions%2fcore/2.0.2/2.0.3?slim=true) |

---

### Release Notes

<details>
<summary>actions/toolkit (@&#8203;actions/core)</summary>

### [`v2.0.3`](https://github.com/actions/toolkit/blob/HEAD/packages/core/RELEASES.md#203)

- Bump `@actions/http-client` to `3.0.2`

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #67
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-01-28 00:30:51 +01:00
RenovateandPhilipp c07372bfa5 chore(deps): update vitest monorepo to v4.0.18 (#65)
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://github.com/vitest-dev/vitest/tree/main/packages/coverage-v8#readme) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8)) | [`4.0.17` → `4.0.18`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.0.17/4.0.18) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.0.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.0.17/4.0.18?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.0.17` → `4.0.18`](https://renovatebot.com/diffs/npm/vitest/4.0.17/4.0.18) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.0.18?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.0.17/4.0.18?slim=true) |

---

### Release Notes

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

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

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

#####    🚀 Experimental Features

- **experimental**: Add `onModuleRunner` hook to `worker.init`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9286](https://github.com/vitest-dev/vitest/issues/9286) [<samp>(ea837)</samp>](https://github.com/vitest-dev/vitest/commit/ea837de7d)

#####    🐞 Bug Fixes

- Use `meta.url` in `createRequire`  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9441](https://github.com/vitest-dev/vitest/issues/9441) [<samp>(e0572)</samp>](https://github.com/vitest-dev/vitest/commit/e057281ca)
- **browser**: Hide injected data-testid attributes  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9503](https://github.com/vitest-dev/vitest/issues/9503) [<samp>(f8989)</samp>](https://github.com/vitest-dev/vitest/commit/f89899cd8)
- **ui**: Process artifact attachments when generating HTML reporter  -  by [@&#8203;macarie](https://github.com/macarie) in [#&#8203;9472](https://github.com/vitest-dev/vitest/issues/9472) [<samp>(22543)</samp>](https://github.com/vitest-dev/vitest/commit/225435647)

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

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi44OS4wIiwidXBkYXRlZEluVmVyIjoiNDIuODkuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiQXV0b21hdGlvbiJdfQ==-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/65
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-01-25 00:09:09 +01:00
RenovateandPhilipp d918fd764c chore(deps): update vitest monorepo to v4.0.17 (#63)
Lint / pre-commit Linting (push) Successful in 1m11s
Test / Run Tests (push) Successful in 1m13s
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://github.com/vitest-dev/vitest/tree/main/packages/coverage-v8#readme) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/coverage-v8)) | [`4.0.16` → `4.0.17`](https://renovatebot.com/diffs/npm/@vitest%2fcoverage-v8/4.0.16/4.0.17) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fcoverage-v8/4.0.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fcoverage-v8/4.0.16/4.0.17?slim=true) |
| [vitest](https://vitest.dev) ([source](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.0.16` → `4.0.17`](https://renovatebot.com/diffs/npm/vitest/4.0.16/4.0.17) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.0.17?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.0.16/4.0.17?slim=true) |

---

### Release Notes

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

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

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

#####    🚀 Experimental Features

- Support openTelemetry for browser mode  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9180](https://github.com/vitest-dev/vitest/issues/9180) [<samp>(1ec3a)</samp>](https://github.com/vitest-dev/vitest/commit/1ec3a8b68)
- Support TRACEPARENT and TRACESTATE environment variables for OpenTelemetry context propagation  -  by [@&#8203;Copilot](https://github.com/Copilot), **hi-ogawa** and [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9295](https://github.com/vitest-dev/vitest/issues/9295) [<samp>(876cb)</samp>](https://github.com/vitest-dev/vitest/commit/876cb84c2)

#####    🐞 Bug Fixes

- Improve asymmetric matcher diff readability by unwrapping container matchers  -  by [@&#8203;Copilot](https://github.com/Copilot), **sheremet-va**, **hi-ogawa** and [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9330](https://github.com/vitest-dev/vitest/issues/9330) [<samp>(b2ec7)</samp>](https://github.com/vitest-dev/vitest/commit/b2ec724a8)
- Improve runner error when importing outside of test context  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9335](https://github.com/vitest-dev/vitest/issues/9335) [<samp>(2dd3d)</samp>](https://github.com/vitest-dev/vitest/commit/2dd3dd839)
- Replace crypto.randomUUID to allow insecure environments (fix [#&#8203;9](https://github.com/vitest-dev/vitest/issues/9)…  -  by [@&#8203;plusgut](https://github.com/plusgut) in [#&#8203;9339](https://github.com/vitest-dev/vitest/issues/9339) and [#&#8203;9](https://github.com/vitest-dev/vitest/issues/9) [<samp>(e6a3f)</samp>](https://github.com/vitest-dev/vitest/commit/e6a3f8cc7)
- Handle null options in `addEventHandler` [#&#8203;9371](https://github.com/vitest-dev/vitest/issues/9371)  -  by [@&#8203;ThibautMarechal](https://github.com/ThibautMarechal) in [#&#8203;9372](https://github.com/vitest-dev/vitest/issues/9372) and [#&#8203;9371](https://github.com/vitest-dev/vitest/issues/9371) [<samp>(40841)</samp>](https://github.com/vitest-dev/vitest/commit/40841ff00)
- Typo in browser.provider error  -  by [@&#8203;deammer](https://github.com/deammer) in [#&#8203;9394](https://github.com/vitest-dev/vitest/issues/9394) [<samp>(4b67f)</samp>](https://github.com/vitest-dev/vitest/commit/4b67fc25a)
- **browser**:
  - Fix `process.env` and `import.meta.env` defines in inline project  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9239](https://github.com/vitest-dev/vitest/issues/9239) [<samp>(b70c9)</samp>](https://github.com/vitest-dev/vitest/commit/b70c96121)
  - Fix upload File instance  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9294](https://github.com/vitest-dev/vitest/issues/9294) [<samp>(b6778)</samp>](https://github.com/vitest-dev/vitest/commit/b67788c69)
  - Fix invalid project token for artifacts assets  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9321](https://github.com/vitest-dev/vitest/issues/9321) [<samp>(caa7d)</samp>](https://github.com/vitest-dev/vitest/commit/caa7d73d4)
  - Log `ErrorEvent.message` when unhandled `ErrorEvent.error` is null  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9322](https://github.com/vitest-dev/vitest/issues/9322) [<samp>(5d84e)</samp>](https://github.com/vitest-dev/vitest/commit/5d84eeb91)
  - Support `fileParallelism` on an instance  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9328](https://github.com/vitest-dev/vitest/issues/9328) [<samp>(15006)</samp>](https://github.com/vitest-dev/vitest/commit/150065459)
- **coverage**:
  - Remove unnecessary `istanbul-lib-source-maps` usage  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9344](https://github.com/vitest-dev/vitest/issues/9344) [<samp>(b0940)</samp>](https://github.com/vitest-dev/vitest/commit/b09405375)
  - Apply patch from [istanbuljs/istanbuljs#837](https://github.com/istanbuljs/istanbuljs/issues/837)  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) and **sapphi-red** in [#&#8203;9413](https://github.com/vitest-dev/vitest/issues/9413) and [#&#8203;837](https://github.com/vitest-dev/vitest/issues/837) [<samp>(e05ce)</samp>](https://github.com/vitest-dev/vitest/commit/e05cedbf4)
- **fsModuleCache**:
  - Don't store importers in cache  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9422](https://github.com/vitest-dev/vitest/issues/9422) [<samp>(75136)</samp>](https://github.com/vitest-dev/vitest/commit/751364eec)
  - Add importers alongside importedModules  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9423](https://github.com/vitest-dev/vitest/issues/9423) [<samp>(59f92)</samp>](https://github.com/vitest-dev/vitest/commit/59f92d403)
- **mocker**:
  - Fix mock transform with class  -  by [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9421](https://github.com/vitest-dev/vitest/issues/9421) [<samp>(d390e)</samp>](https://github.com/vitest-dev/vitest/commit/d390eb527)
- **pool**:
  - Validate environment options when reusing the worker  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9349](https://github.com/vitest-dev/vitest/issues/9349) [<samp>(a8a88)</samp>](https://github.com/vitest-dev/vitest/commit/a8a8836e3)
  - Handle worker start failures gracefully  -  by [@&#8203;AriPerkkio](https://github.com/AriPerkkio) in [#&#8203;9337](https://github.com/vitest-dev/vitest/issues/9337) [<samp>(200da)</samp>](https://github.com/vitest-dev/vitest/commit/200dadb32)
- **reporter**:
  - Report test module if it failed to run  -  by [@&#8203;sheremet-va](https://github.com/sheremet-va) in [#&#8203;9272](https://github.com/vitest-dev/vitest/issues/9272) [<samp>(c7888)</samp>](https://github.com/vitest-dev/vitest/commit/c78882985)
- **runner**:
  - Respect nested test.only within describe.only  -  by [@&#8203;Ujjwaljain16](https://github.com/Ujjwaljain16) in [#&#8203;9021](https://github.com/vitest-dev/vitest/issues/9021) and [#&#8203;9213](https://github.com/vitest-dev/vitest/issues/9213) [<samp>(55d5d)</samp>](https://github.com/vitest-dev/vitest/commit/55d5dad69)
- **typecheck**:
  - Improve error message when tsc outputs help text  -  by [@&#8203;Ujjwaljain16](https://github.com/Ujjwaljain16) in [#&#8203;9214](https://github.com/vitest-dev/vitest/issues/9214) [<samp>(7b10a)</samp>](https://github.com/vitest-dev/vitest/commit/7b10ab4cd)
- **ui**:
  - Detect gzip by magic numbers instead of Content-Type header in html reporter  -  by [@&#8203;Copilot](https://github.com/Copilot), **hi-ogawa** and [@&#8203;hi-ogawa](https://github.com/hi-ogawa) in [#&#8203;9278](https://github.com/vitest-dev/vitest/issues/9278) [<samp>(dd033)</samp>](https://github.com/vitest-dev/vitest/commit/dd0331632)
- **webdriverio**:
  - Fall back to WebDriver Classic [#&#8203;9244](https://github.com/vitest-dev/vitest/issues/9244)  -  by [@&#8203;JustasMonkev](https://github.com/JustasMonkev) in [#&#8203;9373](https://github.com/vitest-dev/vitest/issues/9373) and [#&#8203;9244](https://github.com/vitest-dev/vitest/issues/9244) [<samp>(c23dd)</samp>](https://github.com/vitest-dev/vitest/commit/c23dd11bd)

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

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi43MS40IiwidXBkYXRlZEluVmVyIjoiNDIuNzEuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiQXV0b21hdGlvbiJdfQ==-->

Reviewed-on: https://git.horstenkamp.eu/Screeps/screeps-deploy-action/pulls/63
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-01-16 18:10:19 +01:00
RenovateandPhilipp 4a3dbb707c chore(deps): update dependency @actions/core to v2.0.2 (#62)
Lint / pre-commit Linting (push) Waiting to run
Test / Run Tests (push) Has been cancelled
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [@actions/core](https://github.com/actions/toolkit/tree/main/packages/core) ([source](https://github.com/actions/toolkit/tree/HEAD/packages/core)) | [`2.0.1` → `2.0.2`](https://renovatebot.com/diffs/npm/@actions%2fcore/2.0.1/2.0.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@actions%2fcore/2.0.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@actions%2fcore/2.0.1/2.0.2?slim=true) |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #62
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-01-16 18:09:35 +01:00
RenovateandRenovate bfa059df07 chore(deps): update pre-commit hook macisamuele/language-formatters-pre-commit-hooks to v2.16.0 (#64)
Lint / pre-commit Linting (push) Successful in 1m11s
Test / Run Tests (push) Successful in 1m16s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-01-16 01:28:56 +01:00
Philipp c5a58026fa Remove unused pre-commit hooks (#61)
Test / Run Tests (push) Successful in 9m59s
Lint / pre-commit Linting (push) Successful in 10m6s
### **PR Type**
Enhancement

___

### **Description**
- Remove unused pre-commit hooks

- Delete `pretty-format-ini` and `pretty-format-toml`

___

<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Configuration changes</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>.pre-commit-config.yaml</strong><dd><code>Clean up pre-commit hook configuration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

.pre-commit-config.yaml

<ul><li>Removed <code>pretty-format-ini</code> hook entry<br> <li> Removed <code>pretty-format-toml</code> hook entry</ul>

</details>

  </td>
  <td><a href="https://git.horstenkamp.eu/Screeps/screeps-deploy-action/src/branch/chore/pre-commit-cleanup/.pre-commit-config.yaml">+0/-8</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

</details>

___

Reviewed-on: #61
2026-01-04 23:47:59 +01:00
Philipp c05341c0a7 Enable minification for build artifact (#60)
Lint / pre-commit Linting (push) Successful in 1m29s
Test / Run Tests (push) Successful in 6m9s
### **PR Type**
Enhancement

___

### **Description**
- Enable minification in build script.

- Produce minified `dist/index.js` bundle.

___

<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Configuration changes</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>package.json</strong><dd><code>Add minification flag to build script</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

package.json

- Added `-m` flag to build script.

</details>

  </td>
  <td><a href="https://git.horstenkamp.eu/Screeps/screeps-deploy-action/src/branch/build/minify/package.json">+1/-1</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Build artifact</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>index.js</strong><dd><code>Provide minified distribution bundle</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dist/index.js

- Updated output to minified bundle.

</details>

  </td>
  <td><a href="https://git.horstenkamp.eu/Screeps/screeps-deploy-action/src/branch/build/minify/dist/index.js">+5/-51912</a></td>

</tr>
</table></td></tr></tr></tbody></table>

</details>

___

Reviewed-on: #60
2026-01-04 07:17:33 +01:00
Philipp 6a098d425e Enable minification for build artifact (#60)
### **PR Type**
Enhancement

___

### **Description**
- Enable minification in build script.

- Produce minified `dist/index.js` bundle.

___

<details> <summary><h3> File Walkthrough</h3></summary>

<table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Configuration changes</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>package.json</strong><dd><code>Add minification flag to build script</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

package.json

- Added `-m` flag to build script.

</details>

  </td>
  <td><a href="https://git.horstenkamp.eu/Screeps/screeps-deploy-action/src/branch/build/minify/package.json">+1/-1</a>&nbsp; &nbsp; &nbsp; </td>

</tr>
</table></td></tr><tr><td><strong>Build artifact</strong></td><td><table>
<tr>
  <td>
    <details>
      <summary><strong>index.js</strong><dd><code>Provide minified distribution bundle</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

dist/index.js

- Updated output to minified bundle.

</details>

  </td>
  <td><a href="https://git.horstenkamp.eu/Screeps/screeps-deploy-action/src/branch/build/minify/dist/index.js">+5/-51912</a></td>

</tr>
</table></td></tr></tr></tbody></table>

</details>

___

Reviewed-on: #60
2026-01-04 07:17:05 +01:00
RenovateandRenovate 4a77ba188a chore(deps): update dependency node to v24 (#59)
Lint / pre-commit Linting (push) Successful in 2m38s
Test / Run Tests (push) Successful in 3m24s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-01-04 07:05:42 +01:00
RenovateandRenovate 6c7a0961a7 chore(deps): update actions/setup-node action to v6 (#58)
Lint / pre-commit Linting (push) Has been cancelled
Test / Run Tests (push) Has been cancelled
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2026-01-04 07:05:31 +01:00
Philipp 3ff19001a7 chore: Add a unit testing framework (#49)
Lint / pre-commit Linting (push) Waiting to run
Test / Run Tests (push) Successful in 1m12s
Add vitest as a unit testing framework

Reviewed-on: #49
2026-01-04 07:02:20 +01:00
Philipp a14206d32c chore: enforce linux line endings (#57)
Lint / pre-commit Linting (push) Successful in 54s
Add .gitattributes and .editorconfig to enforce LF line endings. Update devcontainer settings.

Reviewed-on: #57
2026-01-03 21:57:34 +01:00
RenovateandRenovate 8e305f9126 fix(deps): update dependency glob to v13 (#40)
Lint / pre-commit Linting (push) Successful in 45s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-12-31 19:12:17 +01:00
RenovateandRenovate a7552f74fa fix(deps): update dependency @actions/core to v2 (#41)
Lint / pre-commit Linting (push) Successful in 1m3s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-12-31 19:05:31 +01:00
Philipp 3f19a64809 fix: pre-commit hooks should now be installable in dev containers (#56)
Lint / pre-commit Linting (push) Successful in 51s
Reviewed-on: #56
2025-12-31 18:55:42 +01:00
Philipp 97ecdcbc66 Exclude the dist forlder from pre-commit (#54)
Lint / pre-commit Linting (push) Successful in 1m12s
Reviewed-on: #54
2025-12-27 21:10:44 +01:00
Philipp de6b54abe9 feat: use a custom build js step instead of all the dependencies (#53)
Lint / pre-commit Linting (push) Successful in 1m38s
Reviewed-on: #53
2025-12-27 19:25:56 +01:00
Philipp 60d3342a1c chore: ressort whitespace hooks in pre-commit config (#52)
Lint / pre-commit Linting (push) Successful in 1m24s
Reviewed-on: #52
2025-12-27 01:42:41 +01:00
Philipp e5773d3235 chore: format devcontainer.json and lint.yaml for consistency
Lint / pre-commit Linting (push) Successful in 1m23s
2025-12-27 01:39:31 +01:00
Philipp 0d97efec68 chore: ressort whitespace hooks in pre-commit config 2025-12-27 01:35:24 +01:00
RenovateandPhilipp 9e9740b8b1 chore(deps): update mcr.microsoft.com/devcontainers/javascript-node docker tag to v4 (#51)
Lint / pre-commit Linting (push) Successful in 1m24s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| mcr.microsoft.com/devcontainers/javascript-node | image | major | `1-20` → `4-20` |

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #51
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-12-27 01:24:56 +01:00
Philipp 65486738c0 chore: add a devcontainer setup (#50)
Lint / pre-commit Linting (push) Successful in 1m30s
Add a devcontainer setup and remove a few redundent pre-commit hooks

Reviewed-on: #50
2025-12-27 01:02:36 +01:00
Philipp 25e4ba9f71 feat: Add relevant check-jsonschema pre-commit hooks (#48)
Lint / pre-commit Linting (push) Successful in 1m20s
Reviewed-on: #48
2025-12-21 14:03:52 +01:00
Philipp 76eaa21fb3 Add a npm rebuild option to the renovate.json (#47)
Lint / pre-commit Linting (push) Successful in 1m19s
Reviewed-on: #47
2025-12-21 12:22:25 +01:00
Philipp e39a879879 Add a gemini Geminin CLI guid for the LLM and a NPM section that installs npm packages after updates via renovate (#46)
Lint / pre-commit Linting (push) Successful in 1m39s
Reviewed-on: #46
2025-12-21 01:19:31 +01:00
Philipp 291ffc41bc Add a renovate.json schema validation (#45)
Lint / pre-commit Linting (push) Successful in 1m23s
Reviewed-on: #45
2025-12-19 17:51:59 +01:00
Philipp 60c6779c64 Add a renovate.json schema validation hook
Lint / pre-commit Linting (push) Successful in 1m25s
2025-12-19 17:49:54 +01:00
Philipp fb80f152df Add a schema validation for the renovate.json 2025-12-19 17:49:28 +01:00
Philipp 1da1212c8f Run npm-install and add npm install as a post update option (#42)
Lint / pre-commit Linting (push) Successful in 1m35s
Reviewed-on: #42
2025-12-16 19:32:16 +01:00
Philipp a892e71b21 run-npm-install (#44)
Lint / pre-commit Linting (push) Successful in 1m30s
Reviewed-on: #44
2025-12-16 01:10:50 +01:00
Philipp 74d74b73ad Run npm install (#43)
Lint / pre-commit Linting (push) Successful in 1m18s
Reviewed-on: #43
2025-12-16 01:07:55 +01:00
RenovateandRenovate 9510650dd1 chore(deps): update actions/checkout action to v6 (#38)
Lint / pre-commit Linting (push) Successful in 1m40s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-11-20 17:58:48 +01:00
RenovateandPhilipp 96da456932 chore(deps): update dependency screeps-api to v1.16.1 (#34)
Lint / pre-commit Linting (push) Successful in 1m35s
This PR contains the following updates:

| Package | Change | Age | Confidence |
|---|---|---|---|
| [screeps-api](https://github.com/screepers/node-screeps-api) | [`1.7.2` -> `1.16.1`](https://renovatebot.com/diffs/npm/screeps-api/1.7.2/1.16.1) | [![age](https://developer.mend.io/api/mc/badges/age/npm/screeps-api/1.16.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/screeps-api/1.7.2/1.16.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) |

---

### Release Notes

<details>
<summary>screepers/node-screeps-api (screeps-api)</summary>

### [`v1.16.1`](https://github.com/screepers/node-screeps-api/compare/d0f56bfae9203a56e52b5ae3caea94ce856e7e69...8b6ad48b41526c47125807fb83ac279928e53e6e)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/d0f56bfae9203a56e52b5ae3caea94ce856e7e69...8b6ad48b41526c47125807fb83ac279928e53e6e)

### [`v1.16.0`](https://github.com/screepers/node-screeps-api/compare/01712e09ebff7dfc3e51c333142d5da7ad63918a...d0f56bfae9203a56e52b5ae3caea94ce856e7e69)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/01712e09ebff7dfc3e51c333142d5da7ad63918a...d0f56bfae9203a56e52b5ae3caea94ce856e7e69)

### [`v1.15.1`](https://github.com/screepers/node-screeps-api/compare/71390f76ab8601f0f6d73089d31fd7276db8f67c...01712e09ebff7dfc3e51c333142d5da7ad63918a)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/71390f76ab8601f0f6d73089d31fd7276db8f67c...01712e09ebff7dfc3e51c333142d5da7ad63918a)

### [`v1.15.0`](https://github.com/screepers/node-screeps-api/compare/2b10eaebe697ac841d93ea5f0fb46a0ab32159a4...71390f76ab8601f0f6d73089d31fd7276db8f67c)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/2b10eaebe697ac841d93ea5f0fb46a0ab32159a4...71390f76ab8601f0f6d73089d31fd7276db8f67c)

### [`v1.14.0`](https://github.com/screepers/node-screeps-api/compare/07dcce7d6dc9c68b56636b57e15d6fc8dd2a0b4d...2b10eaebe697ac841d93ea5f0fb46a0ab32159a4)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/07dcce7d6dc9c68b56636b57e15d6fc8dd2a0b4d...2b10eaebe697ac841d93ea5f0fb46a0ab32159a4)

### [`v1.13.0`](https://github.com/screepers/node-screeps-api/compare/db1ac1e8416c2cb88d4bd730799eb2e3eeb3f753...07dcce7d6dc9c68b56636b57e15d6fc8dd2a0b4d)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/db1ac1e8416c2cb88d4bd730799eb2e3eeb3f753...07dcce7d6dc9c68b56636b57e15d6fc8dd2a0b4d)

### [`v1.12.2`](https://github.com/screepers/node-screeps-api/compare/38134ddf1eaca38b5c7834db432dc73cc1d1386d...db1ac1e8416c2cb88d4bd730799eb2e3eeb3f753)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/38134ddf1eaca38b5c7834db432dc73cc1d1386d...db1ac1e8416c2cb88d4bd730799eb2e3eeb3f753)

### [`v1.12.1`](https://github.com/screepers/node-screeps-api/compare/eacd92956ee914a2395fe6d1d9381eb51ad64a67...38134ddf1eaca38b5c7834db432dc73cc1d1386d)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/eacd92956ee914a2395fe6d1d9381eb51ad64a67...38134ddf1eaca38b5c7834db432dc73cc1d1386d)

### [`v1.12.0`](https://github.com/screepers/node-screeps-api/compare/29023be68f02cb1d5d1607ef80f207b93fd51ff8...eacd92956ee914a2395fe6d1d9381eb51ad64a67)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/29023be68f02cb1d5d1607ef80f207b93fd51ff8...eacd92956ee914a2395fe6d1d9381eb51ad64a67)

### [`v1.11.1`](https://github.com/screepers/node-screeps-api/compare/dd520bc1cf666a65503bfbea71bc2ec8c4c4102c...29023be68f02cb1d5d1607ef80f207b93fd51ff8)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/dd520bc1cf666a65503bfbea71bc2ec8c4c4102c...29023be68f02cb1d5d1607ef80f207b93fd51ff8)

### [`v1.11.0`](https://github.com/screepers/node-screeps-api/compare/a42856fa88b59218ce2cc0ea228c8871c76857e9...dd520bc1cf666a65503bfbea71bc2ec8c4c4102c)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/a42856fa88b59218ce2cc0ea228c8871c76857e9...dd520bc1cf666a65503bfbea71bc2ec8c4c4102c)

### [`v1.10.0`](https://github.com/screepers/node-screeps-api/compare/4e15d49c45e9b5cc3808122db6597c2d45537cb5...a42856fa88b59218ce2cc0ea228c8871c76857e9)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/4e15d49c45e9b5cc3808122db6597c2d45537cb5...a42856fa88b59218ce2cc0ea228c8871c76857e9)

### [`v1.9.1`](https://github.com/screepers/node-screeps-api/compare/866a7e4c40a476f1775a4e0a1357a5db46a2fdc5...4e15d49c45e9b5cc3808122db6597c2d45537cb5)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/866a7e4c40a476f1775a4e0a1357a5db46a2fdc5...4e15d49c45e9b5cc3808122db6597c2d45537cb5)

### [`v1.9.0`](https://github.com/screepers/node-screeps-api/compare/7d7116d4a702b412ce6fa2139cf393fb05bf4369...866a7e4c40a476f1775a4e0a1357a5db46a2fdc5)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/7d7116d4a702b412ce6fa2139cf393fb05bf4369...866a7e4c40a476f1775a4e0a1357a5db46a2fdc5)

### [`v1.8.1`](https://github.com/screepers/node-screeps-api/compare/dd019cb14030c87591e2a10867071a8b0bed5e8d...7d7116d4a702b412ce6fa2139cf393fb05bf4369)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/dd019cb14030c87591e2a10867071a8b0bed5e8d...7d7116d4a702b412ce6fa2139cf393fb05bf4369)

### [`v1.8.0`](https://github.com/screepers/node-screeps-api/compare/4d23ab0dd0cf3a719cbe9b3491e1f3f7900bc5d6...dd019cb14030c87591e2a10867071a8b0bed5e8d)

[Compare Source](https://github.com/screepers/node-screeps-api/compare/4d23ab0dd0cf3a719cbe9b3491e1f3f7900bc5d6...dd019cb14030c87591e2a10867071a8b0bed5e8d)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #34
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-11-02 22:46:51 +01:00
RenovateandRenovate d95bfe4b61 chore(deps): update actions/checkout action to v5 (#33)
Lint / pre-commit Linting (push) Successful in 1m37s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-09-06 15:18:55 +02:00
RenovateandRenovate 19259737af chore(deps): update actions/setup-python action to v6 (#29)
Lint / pre-commit Linting (push) Successful in 1m25s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-09-05 02:50:53 +02:00
RenovateandRenovate ac36a7ab1f chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v6 (#31)
Lint / pre-commit Linting (push) Successful in 1m33s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-09-05 02:47:08 +02:00
RenovateandRenovate 0780c1e8ab chore(deps): update pre-commit hook macisamuele/language-formatters-pre-commit-hooks to v2.15.0 (#30)
Lint / pre-commit Linting (push) Successful in 2m3s
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-09-05 02:43:09 +02:00
Philipp 9d9dc1e822 Update renovate.json (#28)
Lint / pre-commit Linting (push) Successful in 2m0s
Reviewed-on: #28
2025-09-05 02:40:16 +02:00
RenovateandPhilipp 82e4d66d61 chore(deps): update actions/setup-python action to v5 (#26)
Lint / pre-commit Linting (push) Successful in 1m43s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [actions/setup-python](https://github.com/actions/setup-python) | action | major | `v4` -> `v5` |

---

### Release Notes

<details>
<summary>actions/setup-python (actions/setup-python)</summary>

### [`v5`](https://github.com/actions/setup-python/compare/v4...v5)

[Compare Source](https://github.com/actions/setup-python/compare/v4...v5)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #26
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-08-10 16:02:47 +02:00
RenovateandPhilipp 01deab89cd chore(deps): update dependency glob to v11.0.3 (#24)
Lint / pre-commit Linting (push) Successful in 1m48s
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [glob](https://github.com/isaacs/node-glob) | dependencies | patch | [`11.0.1` -> `11.0.3`](https://renovatebot.com/diffs/npm/glob/11.0.1/11.0.3) |

---

### Release Notes

<details>
<summary>isaacs/node-glob (glob)</summary>

### [`v11.0.3`](https://github.com/isaacs/node-glob/compare/v11.0.2...v11.0.3)

[Compare Source](https://github.com/isaacs/node-glob/compare/v11.0.2...v11.0.3)

### [`v11.0.2`](https://github.com/isaacs/node-glob/compare/v11.0.1...v11.0.2)

[Compare Source](https://github.com/isaacs/node-glob/compare/v11.0.1...v11.0.2)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), 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 this update again.

---

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

---

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

Reviewed-on: #24
Co-authored-by: Renovate Bot <renovate@horstenkamp.eu>
Co-committed-by: Renovate Bot <renovate@horstenkamp.eu>
2025-08-10 15:14:39 +02:00
Philipp 28b0ab9f02 chore: Configure Renovate (#23)
Lint / pre-commit Linting (push) Successful in 2m3s
Reviewed-on: #23
2025-08-10 15:01:15 +02:00
Renovate 0c37ffc7c7 Add renovate.json
Lint / pre-commit Linting (push) Successful in 1m0s
2025-05-10 08:53:56 +00:00
Philipp Horstenkamp 9c79fe03b0 Delete .gitea/workflows/maintenance.yaml (#22)
Lint / pre-commit Linting (push) Successful in 59s
Reviewed-on: #22
2025-05-10 10:44:20 +02:00
PhilippandPhilipp Horstenkamp 6f0f75d7ad Fixed glob import (#21)
Lint / pre-commit Linting (push) Successful in 32s
Glob was not imported as a function

Reviewed-on: #21
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
2025-04-21 21:00:29 +02:00
PhilippandPhilipp Horstenkamp 318515b9c4 Update dependencies (#20)
Lint / pre-commit Linting (push) Successful in 29s
- @actions/core
- glob
- screeps-api

Reviewed-on: #20
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
2025-04-12 13:43:45 +02:00
PhilippandPhilipp Horstenkamp 1aa7be2b73 Moved execution time (#19)
Lint / pre-commit Linting (push) Successful in 52s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Successful in 1m52s
Reviewed-on: #19
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
2025-04-02 23:41:12 +02:00
runnerandPhilipp Horstenkamp 567eb22cd8 Updates to the pre-commit action created at 2025-01-12 (#18)
Lint / pre-commit Linting (push) Successful in 35s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Failing after 14m52s
Update to the pre-commit action.

Reviewed-on: #18
Co-authored-by: runner <git@horstenkamp.eu>
Co-committed-by: runner <git@horstenkamp.eu>
2025-03-16 21:50:53 +01:00
Philipp Horstenkamp 898a9aae6d Updates to the pre-commit action created at 2024-07-20 (#17)
Lint / pre-commit Linting (push) Successful in 46s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Failing after 11m47s
Reviewed-on: #17
2024-07-21 14:53:02 +02:00
runner 3b0920a44a chore: update pre-commit hooks
Lint / pre-commit Linting (push) Successful in 47s
2024-07-20 22:07:01 +00:00
Philipp Horstenkamp dec00551b1 Set default value to commiter and reviewer (#16)
Lint / pre-commit Linting (push) Successful in 47s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Successful in 1m52s
Reviewed-on: #16
2024-05-24 02:24:17 +02:00
Philipp Horstenkamp 8309164978 Added an assigne as a variable.
Lint / pre-commit Linting (push) Successful in 45s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Successful in 1m43s
2024-05-24 02:20:23 +02:00
Philipp Horstenkamp 7f0b32347e Set default value to commiter and reviewer
Lint / pre-commit Linting (push) Successful in 48s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Successful in 1m37s
2024-05-24 02:09:00 +02:00
PhilippandPhilipp Horstenkamp 508eec34d7 Add an replacement for the {{hostname}} token. (#15)
Lint / pre-commit Linting (push) Successful in 49s
Reviewed-on: #15
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
2024-05-23 23:47:07 +02:00
PhilippandPhilipp Horstenkamp bee951fc34 Fixed the copyright note (#14)
Lint / pre-commit Linting (push) Successful in 45s
Reviewed-on: #14
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
2024-05-23 23:05:59 +02:00
3e3d24f787 Updates to the pre-commit action created at 2024-02-17 (#13)
Lint / pre-commit Linting (push) Successful in 25s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Successful in 1m10s
Update to the pre-commit action.

Co-authored-by: Philipp <Philipp@users.noreply.github.com>
Reviewed-on: #13
Co-authored-by: Act Runner <runner@np-replay-git@horstenkamp.eu>
Co-committed-by: Act Runner <runner@np-replay-git@horstenkamp.eu>
2024-04-25 22:39:28 +02:00
PhilippandPhilipp Horstenkamp e342ceca6f Added the ability to replace some placeholders in the screeps code (#12)
Lint / pre-commit Linting (push) Successful in 29s
added the `git-replace` option.
Replaceing {{gitRef}}, {{deployTime}} and {{gitHash}}.

Reviewed-on: #12
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
2023-12-25 02:22:42 +01:00
a039bc1e28 Updates to the pre-commit action created at 2023-12-23 (#11)
Lint / pre-commit Linting (push) Successful in 36s
Update to the pre-commit action.

Co-authored-by: Philipp <Philipp@users.noreply.github.com>
Reviewed-on: #11
Co-authored-by: Act Runner <runner@np-replay-git@horstenkamp.eu>
Co-committed-by: Act Runner <runner@np-replay-git@horstenkamp.eu>
2023-12-24 10:55:35 +01:00
Philipp Horstenkamp 0a952729bc Merge pull request 'Updates to the pre-commit action created at 2023-12-09' (#10) from chore/update-pre-commit into main
Lint / pre-commit Linting (push) Successful in 55s
Reviewed-on: #10
Reviewed-by: Philipp Horstenkamp <philipp@np-replay-git@horstenkamp.eu>
2023-12-09 13:22:43 +01:00
Philippandgithub-actions[bot] f106f83c72 chore: update pre-commit hooks
Lint / pre-commit Linting (push) Successful in 59s
2023-12-09 12:20:40 +00:00
Philipp Horstenkamp cd7745d646 Merge pull request 'Reworked the maintance action to use a PAT' (#8) from maintance-action-with-token into main
Lint / pre-commit Linting (push) Successful in 25s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Successful in 1m24s
Reviewed-on: #8
2023-12-09 13:18:57 +01:00
Philipp 4951253d52 Reworked the maintance action to use a PAT
Lint / pre-commit Linting (push) Successful in 55s
Auto Maintenance Cycle / pre-commit Autoupdate (push) Successful in 1m33s
2023-12-09 13:16:49 +01:00
PhilippandPhilipp Horstenkamp e7678a5f66 Added auth for private servers (#7)
Lint / pre-commit Linting (push) Successful in 45s
Reviewed-on: #7
Co-authored-by: Philipp Horstenkamp <philipp@horstenkamp.de>
Co-committed-by: Philipp Horstenkamp <philipp@horstenkamp.de>
2023-11-26 22:24:16 +01:00
1060 changed files with 5368 additions and 228342 deletions
+38
View File
@@ -0,0 +1,38 @@
{
"name": "JS Dev Container",
"image": "mcr.microsoft.com/devcontainers/javascript-node:4-20",
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/git-lfs:1": {},
"ghcr.io/devcontainers-extra/features/pre-commit:2": {},
"ghcr.io/devcontainers-extra/features/npm-packages:1": {
"packages": "@google/gemini-cli"
}
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"esbenp.prettier-vscode",
"vitest.explorer",
"google.gemini-cli-vscode-ide-companion",
"google.geminicodeassist"
],
"settings": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"files.eol": "\n"
}
}
},
"postStartCommand": "bash .devcontainer/setup.sh",
"remoteUser": "node",
"updateRemoteUserUID": true,
"mounts": [
"source=pipx-venvs,target=/home/node/.local/share/pipx,type=volume",
"source=pipx-bin,target=/home/node/.local/bin,type=volume",
"source=pre-commit-cache,target=/home/node/.cache/pre-commit,type=volume",
"source=${localEnv:USERPROFILE}/.gemini,target=/home/node/.gemini,type=bind",
"source=gvscode-extensions,target=/home/node/.cache/google-vscode-extension,type=volume"
]
}
+11
View File
@@ -0,0 +1,11 @@
#!/bin/bash
# 1. Fix Git Permissions (Critical in Docker)
git config --global --add safe.directory $(pwd)
# In your setup.sh or postCreateCommand
sudo chown -R node:node /home/node/.cache/
sudo chown -R node:node /workspaces/screeps-deploy-action/.git/hooks
# 2. Re-connect Git Hooks
pre-commit install
pre-commit install-hooks
+8
View File
@@ -0,0 +1,8 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+6
View File
@@ -0,0 +1,6 @@
# .gitea/CODEOWNERS
# Gitea's CODEOWNERS uses Go-style Regular Expressions.
# Patterns are evaluated from top to bottom; the last matching rule takes precedence.
# Global owner: Assign @AutoReview to all files
.* @AutoReview
+2 -2
View File
@@ -9,8 +9,8 @@ jobs:
name: pre-commit Linting
runs-on: pi
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
- run: pip install pre-commit
shell: bash
- name: Pre Commit
-44
View File
@@ -1,44 +0,0 @@
name: Auto Maintenance Cycle
on:
push:
paths:
- .gitea/workflows/maintenance.yaml
schedule:
- cron: 0 1 * * 0
workflow_dispatch:
jobs:
auto-update:
name: pre-commit Autoupdate
runs-on: pi64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- run: pip install pre-commit
shell: bash
- run: pre-commit autoupdate
shell: bash
- name: Test pre-commit
run: SKIP=no-commit-to-branch pre-commit run -a
- name: Commit
id: auto-commit-action
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'chore: update pre-commit hooks'
branch: update/pre-commit-hooks
push_options: --force
create_branch: true
- name: Generate Date
run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV
- run: echo "${{steps.auto-commit-action.outputs.changes_detected}}"
- name: Create an PR action
if: steps.auto-commit-action.outputs.changes_detected == 'true'
uses: https://git.horstenkamp.eu/Philipp/gitea-act-create-pr@main
with:
token: ${{ secrets.REPO_TOKEN }}
branch: update/pre-commit-hooks
title: Updates to the pre-commit action created at ${{ env.CURRENT_DATE }}
body: Update to the pre-commit action.
assignees: Philipp
reviewers: Philipp
+19
View File
@@ -0,0 +1,19 @@
name: Test
on:
push:
workflow_dispatch:
jobs:
test:
name: Run Tests
runs-on: pi
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'
- run: npm install
shell: bash
- run: npm test
shell: bash
+1083
View File
File diff suppressed because it is too large Load Diff
+39 -44
View File
@@ -1,51 +1,46 @@
exclude: ^node_modules/
exclude: ^(dist|node_modules)/
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v6.0.0
hooks:
- id: check-yaml
- id: check-json
- id: check-added-large-files
args: [--enforce-all]
exclude: ^dist/index\.js$
- id: detect-private-key
- id: check-case-conflict
- id: check-symlinks
- id: pretty-format-json
args: [--autofix, --no-sort-keys, --no-ensure-ascii]
- id: check-merge-conflict
- id: no-commit-to-branch
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.16.0
hooks:
- id: pretty-format-yaml
args: [--autofix]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.8
hooks:
- id: prettier
types_or: [css, javascript]
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.37.3
hooks:
- id: check-renovate
- id: check-github-actions
- id: check-github-workflows
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: end-of-file-fixer
exclude: (.txt$|.ipynb$|README.md$|readme.mde$)
- id: trailing-whitespace
exclude: (.txt$|README.md$)
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-xml
- id: check-added-large-files
args: [--enforce-all]
- id: name-tests-test
- id: detect-private-key
- id: check-case-conflict
- id: check-symlinks
- id: check-docstring-first
- id: pretty-format-json
args: [--autofix, --no-sort-keys, --no-ensure-ascii]
- id: check-merge-conflict
- id: no-commit-to-branch
args: [--branch, main]
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: v2.11.0
hooks:
- id: pretty-format-ini
args: [--autofix]
- id: pretty-format-toml
args: [--autofix]
- id: pretty-format-yaml
args: [--autofix]
- repo: https://github.com/frnmst/md-toc
rev: 8.2.2
hooks:
- id: md-toc
- repo: https://github.com/Lucas-C/pre-commit-hooks-java
rev: 1.3.10
hooks:
- id: validate-html
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
types_or: [css, javascript]
- id: mixed-line-ending
args: [--fix=lf]
+13
View File
@@ -0,0 +1,13 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "test",
"group": "test",
"problemMatcher": [],
"label": "npm: test",
"detail": "jest"
}
]
}
+34
View File
@@ -0,0 +1,34 @@
# Gemini Actions
This repository is maintained by Gemini.
## Development Guidelines
* **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
* On request, this repository should be compared against the rules and guidelines specified in the `README.md` of the reference repository: `https://git.horstenkamp.eu/Philipp/template-git`.
## Testing
This project uses [Vitest](https://vitest.dev/) for testing. The tests are located in the `__tests__` directory.
To run the tests locally, use the following command:
```bash
npm test
```
### Testing Pipeline
The tests are automatically run on every push and workflow dispatch using a Gitea workflow. The workflow is defined in `.gitea/workflows/test.yaml`. All testing for this repository is done via Gitea workflows, not GitHub workflows.
The Gitea workflow does the following:
1. Checks out the code.
2. Sets up Node.js.
3. Installs the dependencies using `npm install`.
4. Runs the tests using `npm test`.
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2023 Screeps
Copyright (c) 2023 Philipp Horstenkamp
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+121 -1
View File
@@ -1,3 +1,123 @@
# screeps-deploy-action
This action deploys screeps code via github / gitea actions.
## Introduction
This GitHub Action facilitates the uploading of code to a Screeps server.
It's designed to automate the process of deploying JavaScript code to your Screeps account, ensuring that your game logic is consistently and efficiently updated.
Prerequisites
A Screeps account with an access token.
A GitHub or Gitea repository with your Screeps code.
## Usage
To use this action, you need to set it up in your workflow .yml file located in the .github/workflows directory of your repository.
## Inputs
- `protocol`: The protocol to use (default: https).
- `hostname`: The hostname of the Screeps server (default: screeps.com).
- `port`: The port to use (default: 443).
- `path`: The path for the API (default: /).
- `token`: Authentication token for Screeps.
- `username`: Username for Screeps account (used if no token is provided).
- `password`: Password for Screeps account (used if no token is provided).
- `prefix`: Directory prefix for file paths.
- `pattern`: Glob pattern to match files (default: *.js).
- `branch`: Branch in Screeps to which the code will be uploaded (default: default).
- `git-replace`: Overwrite "{{gitRef}}", "{{gitHash}}" and "{{deployTime}}" values in files matching the pattern.
- `shard`: The Screeps shard to monitor (e.g. `shard0`, `shard1`). Defaults to `shard0` on the official server.
- `monitor`: Number of game ticks to monitor the Screeps console after deploying (0 = disabled, default: 0).
- `log_to_file`: If `true`, buffers stdout to an artifact file instead of streaming live (default: false). Note: Errors and warnings always stream live.
- `on_traceback`: Action on JS traceback detection: `ignore`, `warn`, or `fail` (default: `fail`).
- `on_error_log`: Action on Screeps error-console output: `ignore`, `warn`, or `fail` (default: `warn`).
- `on_warning_log`: Action on `console.warn` output: `ignore`, `warn`, or `fail` (default: `ignore`).
- `monitor_interval`: Print a progress update every N ticks (default: 10).
## Outputs
- `saw_traceback`: `true` if a JS traceback was detected during monitoring.
- `saw_error_log`: `true` if the Screeps error console had output during monitoring.
- `saw_warning_log`: `true` if `console.warn` output was detected during monitoring.
Example Workflow
Create a `.yml` file (e.g., `screeps-deploy.yml`) in your repository's `.github/workflows` directory or `.gitea/workflows` directory:
```yaml
name: Deploy to Screeps
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Upload to Screeps
uses: Screeps/screeps-deploy-action@main
with:
token: ${{ secrets.SCREEPS_TOKEN }}
pattern: '**/*.js'
branch: 'default'
git-replace: /some_options.js
```
In this example:
- The action runs on every push to the repository.
- It checks out your code.
- Then, it uses the "Upload to Screeps" action to deploy the code to your Screeps account.
- You need to set SCREEPS_TOKEN in your repository secrets.
## Advanced Usage
Please note that you can easily filter your deployment branches in the push action.
Multiple deploy steps or jobs are recomended for mulitple deployments.
## Development & Build
This project includes a build step to prepare the action for use.
### Why a Build Step?
We use a build step to compile the JavaScript code and its dependencies into a single, self-contained file (`dist/index.js`). This approach is chosen for the following reasons:
1. **Zero-configuration at Runtime:** By bundling everything, the action runner doesn't need to run `npm install`, which speeds up the action execution.
2. **Reliability:** It guarantees that the exact versions of dependencies tested during development are used in the action, avoiding potential issues with version updates or missing packages.
3. **Standard Practice:** This is a recommended practice for JavaScript-based GitHub/Gitea Actions.
### The Tool: @vercel/ncc
We chose [`@vercel/ncc`](https://github.com/vercel/ncc) as the compiler because:
- It is designed specifically for compiling Node.js modules into a single file.
- It handles native dependencies and dynamic requires intelligently.
- It is zero-config, making the setup simple and maintainable.
### How to Build
If you modify `index.js` or change dependencies, you must rebuild the project before committing:
```bash
npm install
npm run build
```
This will update the `dist/index.js` file, which should be committed to the repository.
### Cross-Platform Compatibility
To ensure this action runs on all platforms (including ARM devices like Raspberry Pi), we explicitly exclude optional native dependencies (`utf-8-validate` and `bufferutil`) from the build. This forces the underlying `ws` library to use its pure JavaScript fallback implementation.
### Build Protocol
| Metric | Status / Value |
| :--- | :--- |
| **Last Build** | Saturday, December 27, 2025 |
| **Build Tool** | `@vercel/ncc` |
| **Target Runtime** | Node.js 20 |
| **Artifact** | `dist/index.js` (Self-contained) |
| **Native Binaries** | None (Excluded for cross-platform support) |
| **Compatibility** | Linux (x64/ARM), Windows, macOS |
| **Build Status** | ✅ Success |
+348
View File
@@ -0,0 +1,348 @@
import { vi, describe, it, expect, beforeEach } from "vitest";
// Mock @actions/core for all tests in this file
vi.mock("@actions/core", () => ({
getInput: vi.fn(),
getBooleanInput: vi.fn(),
info: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
setFailed: vi.fn(),
setOutput: vi.fn(),
}));
// Mock monitor.js so postCode() integration tests don't open a real socket
vi.mock("../monitor.js", () => ({
monitorConsole: vi.fn().mockResolvedValue({
sawTraceback: false,
sawErrorLog: false,
sawWarningLog: false,
}),
}));
// 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";
import {
validateAuthentication,
replacePlaceholders,
readReplaceAndWriteFiles,
readFilesIntoDict,
applyOnAction,
postCode,
} from "../index.js";
import { ScreepsAPI } from "screeps-api";
import fs from "fs";
import path from "path";
import os from "os";
import { glob } from "glob";
describe("validateAuthentication", () => {
it("should return null when only token is provided", () => {
expect(validateAuthentication("token", null, null)).toBeNull();
});
it("should return an error message when token and username are provided", () => {
expect(validateAuthentication("token", "user", null)).toBe(
"Token is defined along with username and/or password.",
);
});
it("should return an error message when token and password are provided", () => {
expect(validateAuthentication("token", null, "pass")).toBe(
"Token is defined along with username and/or password.",
);
});
it("should return an error message when token, username, and password are provided", () => {
expect(validateAuthentication("token", "user", "pass")).toBe(
"Token is defined along with username and/or password.",
);
});
it("should return an error message when no credentials are provided", () => {
expect(validateAuthentication(null, null, null)).toBe(
"Neither token nor password and username are defined.",
);
});
it("should return an error message when only username is provided", () => {
expect(validateAuthentication(null, "user", null)).toBe(
"Username is defined but no password is provided.",
);
});
it("should return an error message when only password is provided", () => {
expect(validateAuthentication(null, null, "pass")).toBe(
"Password is defined but no username is provided.",
);
});
it("should return null when username and password are provided", () => {
expect(validateAuthentication(null, "user", "pass")).toBeNull();
});
});
describe("replacePlaceholders", () => {
beforeEach(() => {
process.env.GITHUB_SHA = "test-sha";
process.env.GITHUB_REF = "test-ref";
});
it("should replace all placeholders", () => {
const content =
"hash: {{gitHash}}, ref: {{gitRef}}, time: {{deployTime}}, host: {{hostname}}";
const replacedContent = replacePlaceholders(content, "test-host");
expect(replacedContent).toMatch(/hash: test-sha/);
expect(replacedContent).toMatch(/ref: test-ref/);
expect(replacedContent).toMatch(/time: .*/);
expect(replacedContent).toMatch(/host: test-host/);
});
});
describe("readReplaceAndWriteFiles", () => {
let tempDir;
beforeEach(async () => {
tempDir = await fs.promises.mkdtemp(
path.join(os.tmpdir(), "replace-test-"),
);
process.env.GITHUB_SHA = "test-sha";
process.env.GITHUB_REF = "test-ref";
});
afterEach(async () => {
if (tempDir) {
await fs.promises.rm(tempDir, { recursive: true, force: true });
}
});
it("should find files and replace placeholders", async () => {
const fileName = "test.js";
const filePath = path.join(tempDir, fileName);
const content = "hash: {{gitHash}}, ref: {{gitRef}}, host: {{hostname}}";
await fs.promises.writeFile(filePath, content);
const pattern = "*.js";
// We pass tempDir as the prefix so glob searches inside it
await readReplaceAndWriteFiles(pattern, tempDir, "test-host");
const updatedContent = await fs.promises.readFile(filePath, "utf8");
expect(updatedContent).toContain("hash: test-sha");
expect(updatedContent).toContain("ref: test-ref");
expect(updatedContent).toContain("host: test-host");
});
});
describe("readFilesIntoDict", () => {
let tempDir;
beforeEach(async () => {
tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "read-test-"));
await fs.promises.mkdir(path.join(tempDir, "subdir"), { recursive: true });
});
afterEach(async () => {
if (tempDir) {
await fs.promises.rm(tempDir, { recursive: true, force: true });
}
});
it("should read files into a dictionary with correct keys", async () => {
const file1 = "file1.js";
const content1 = "content1";
await fs.promises.writeFile(path.join(tempDir, file1), content1);
const file2 = "subdir/file2.js";
const content2 = "content2";
await fs.promises.writeFile(path.join(tempDir, file2), content2);
const pattern = "**/*.js";
const result = await readFilesIntoDict(pattern, tempDir);
// Keys should be relative paths without extension
// On Windows, the path separator might differ, so we should be careful or just check contents
// Based on implementation:
// key = key.slice(prefix.length);
// key = path.basename(key, path.extname(key)); // Drop the file suffix -> THIS IS BUGGY for subdirs?
// Let's check the implementation of readFilesIntoDict again in index.js
// It does: key = path.basename(key, path.extname(key));
// This removes the directory part! So subdir/file2.js becomes file2
expect(result["file1"]).toBe(content1);
expect(result["file2"]).toBe(content2);
});
});
describe("glob functionality", () => {
let tempDir;
beforeEach(async () => {
tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "glob-test-"));
await fs.promises.mkdir(path.join(tempDir, "lib"), { recursive: true });
await fs.promises.mkdir(path.join(tempDir, "deep", "folder"), {
recursive: true,
});
await fs.promises.writeFile(path.join(tempDir, "main.js"), "content");
await fs.promises.writeFile(path.join(tempDir, "utils.js"), "content");
await fs.promises.writeFile(
path.join(tempDir, "lib", "helper.js"),
"content",
);
await fs.promises.writeFile(
path.join(tempDir, "lib", "data.json"),
"content",
);
await fs.promises.writeFile(
path.join(tempDir, "deep", "folder", "main.js"),
"content",
);
});
afterEach(async () => {
if (tempDir) {
await fs.promises.rm(tempDir, { recursive: true, force: true });
}
});
it("should find all javascript files in the directory", async () => {
// Ensure pattern uses forward slashes for glob
const pattern = path.join(tempDir, "**", "*.js").split(path.sep).join("/");
const files = await glob(pattern);
// Normalize file paths to system separator (backslashes on Windows)
const normalizedFiles = files.map((f) => path.normalize(f));
const expectedFiles = [
path.join(tempDir, "deep", "folder", "main.js"),
path.join(tempDir, "lib", "helper.js"),
path.join(tempDir, "main.js"),
path.join(tempDir, "utils.js"),
].sort();
expect(normalizedFiles.sort()).toEqual(expectedFiles);
});
});
// ────────────────────────────────────────────────────────────────────────────
// applyOnAction
// ────────────────────────────────────────────────────────────────────────────
describe("applyOnAction", () => {
beforeEach(() => vi.clearAllMocks());
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, 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, 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, returns false", () => {
expect(applyOnAction("fail", false, "boom")).toBe(false);
expect(core.setFailed).not.toHaveBeenCalled();
});
it("'warn' + false → no core call, returns false", () => {
expect(applyOnAction("warn", false, "msg")).toBe(false);
expect(core.warning).not.toHaveBeenCalled();
});
});
// ────────────────────────────────────────────────────────────────────────────
// postCode — monitor wiring
// ────────────────────────────────────────────────────────────────────────────
describe("postCode — monitor wiring", () => {
beforeEach(() => {
vi.clearAllMocks();
// 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.mockImplementation((name) => {
if (name === "rollback_on_failure") return false;
return false;
});
});
it("does not call monitorConsole when monitor=0 (default)", async () => {
// We just run postCode with monitor=0 and verify monitorConsole is not called.
await postCode();
expect(monitorConsole).not.toHaveBeenCalled();
});
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 "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;
});
// Simulate a failure in monitorConsole
monitorConsole.mockResolvedValueOnce({
sawTraceback: true, // Should trigger "fail" due to on_traceback=fail
sawErrorLog: false,
sawWarningLog: false,
});
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",
);
});
});
+633
View File
@@ -0,0 +1,633 @@
import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
// ── mock @actions/core so tests never touch real CI outputs ─────────────────
vi.mock("@actions/core", () => ({
info: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
setFailed: vi.fn(),
setOutput: vi.fn(),
startGroup: vi.fn(),
endGroup: vi.fn(),
}));
import * as artifact from "@actions/artifact";
vi.mock("@actions/artifact", () => {
const mockClient = {
uploadArtifact: vi.fn().mockResolvedValue({}),
};
return {
create: vi.fn(() => mockClient),
};
});
import * as core from "@actions/core";
import fs from "fs";
import os from "os";
import path from "path";
import {
isOfficialServer,
buildSubscribePath,
detectTraceback,
detectWarning,
outputMultiline,
buildProgressMessage,
writeLogFile,
uploadLogArtifacts,
handleConsoleEvent,
monitorConsole,
} from "../monitor.js";
// ────────────────────────────────────────────────────────────────────────────
// Pure helpers
// ────────────────────────────────────────────────────────────────────────────
describe("isOfficialServer", () => {
it("returns true for screeps.com", () => {
expect(isOfficialServer("screeps.com")).toBe(true);
});
it("returns false for a private hostname", () => {
expect(isOfficialServer("builder64")).toBe(false);
});
it("returns false for an IP address", () => {
expect(isOfficialServer("192.168.1.10")).toBe(false);
});
});
describe("buildSubscribePath", () => {
it("returns console for official server (no shard provided)", () => {
expect(buildSubscribePath("screeps.com")).toBe("console");
});
it("returns console for private server (no shard provided)", () => {
expect(buildSubscribePath("builder64")).toBe("console");
});
it("returns console when shard is provided (official)", () => {
expect(buildSubscribePath("screeps.com", "shard3")).toBe("console");
});
it("returns console when shard is provided (private)", () => {
expect(buildSubscribePath("builder64", "myshard")).toBe("console");
});
});
describe("detectTraceback", () => {
it("returns true when error contains a stack frame line", () => {
const error =
"TypeError: Cannot read properties of undefined\n at Object.<anonymous> (main:1:42)";
expect(detectTraceback(error)).toBe(true);
});
it("returns false for a plain error message without stack frames", () => {
expect(detectTraceback("Something went wrong")).toBe(false);
});
it("returns false for null", () => {
expect(detectTraceback(null)).toBe(false);
});
it("returns false for undefined", () => {
expect(detectTraceback(undefined)).toBe(false);
});
it("returns false for an empty string", () => {
expect(detectTraceback("")).toBe(false);
});
});
describe("detectWarning", () => {
it("returns true for a line with orange font tag", () => {
const lines = ["<font color='orange'>WARN: low energy</font>"];
expect(detectWarning(lines)).toBe(true);
});
it("returns true for a line with yellow font tag", () => {
const lines = ['<font color="yellow">WARN: something</font>'];
expect(detectWarning(lines)).toBe(true);
});
it("returns false for a plain log line", () => {
expect(detectWarning(["Tick 123: harvesting"])).toBe(false);
});
it("returns false for an empty array", () => {
expect(detectWarning([])).toBe(false);
});
it("returns false for null", () => {
expect(detectWarning(null)).toBe(false);
});
it("returns true when only one line in a mixed array is a warning", () => {
const lines = ["normal line", "<font color='orange'>warn</font>"];
expect(detectWarning(lines)).toBe(true);
});
});
describe("outputMultiline", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("prefixes lines with shard when provided", () => {
outputMultiline("line1\nline2", "info", "shard0");
expect(core.info).toHaveBeenCalledWith("[shard0] line1");
expect(core.info).toHaveBeenCalledWith("[shard0] line2");
});
it("does not prefix when shard is missing", () => {
outputMultiline("line1", "info");
expect(core.info).toHaveBeenCalledWith("line1");
});
it("uses core.warning for level=warning", () => {
outputMultiline("warn", "warning", "s0");
expect(core.warning).toHaveBeenCalledWith("[s0] warn");
});
it("uses core.error for level=error", () => {
outputMultiline("err", "error", "s0");
expect(core.error).toHaveBeenCalledWith("[s0] err");
});
});
describe("buildProgressMessage", () => {
it("formats correctly at 0 elapsed", () => {
expect(buildProgressMessage(0, 50)).toBe("[Monitor] 0/50 ticks elapsed...");
});
it("formats correctly midway", () => {
expect(buildProgressMessage(25, 50)).toBe(
"[Monitor] 25/50 ticks elapsed...",
);
});
it("formats correctly at 100%", () => {
expect(buildProgressMessage(50, 50)).toBe(
"[Monitor] 50/50 ticks elapsed...",
);
});
});
describe("uploadLogArtifacts", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("instantiates client and calls uploadArtifact", async () => {
await uploadLogArtifacts(
["/path/to/shard0_console_log.txt"],
"custom-name",
);
expect(artifact.create().uploadArtifact).toHaveBeenCalledWith(
"custom-name",
["/path/to/shard0_console_log.txt"],
"/path/to",
{ continueOnError: true },
);
});
it("does nothing if filePaths is empty", async () => {
await uploadLogArtifacts([]);
expect(artifact.create().uploadArtifact).not.toHaveBeenCalled();
});
});
// ────────────────────────────────────────────────────────────────────────────
// handleConsoleEvent
// ────────────────────────────────────────────────────────────────────────────
describe("handleConsoleEvent", () => {
let state;
let shardBuffers;
beforeEach(() => {
vi.clearAllMocks();
state = { sawTraceback: false, sawErrorLog: false, sawWarningLog: false };
shardBuffers = {};
});
const makeEvent = (log = [], results = [], error = null) => ({
data: { messages: { log, results }, error },
});
it("calls core.info for each stdout line with shard prefix when logToFile=false", () => {
const event = {
data: {
shard: "shard0",
messages: { log: ["line1"], results: ["line2"] },
},
};
handleConsoleEvent(event, { logToFile: false }, shardBuffers, state);
expect(core.info).toHaveBeenCalledWith("[shard0] line1");
expect(core.info).toHaveBeenCalledWith("[shard0] line2");
expect(Object.keys(shardBuffers)).toHaveLength(0);
});
it("buffers stdout when logToFile=true; does not call core.info", () => {
const event = {
data: { shard: "shard0", messages: { log: ["line1", "line2"] } },
};
handleConsoleEvent(event, { logToFile: true }, shardBuffers, state);
expect(core.info).not.toHaveBeenCalled();
expect(shardBuffers["shard0"]).toEqual(["line1", "line2"]);
});
it("includes results lines in output", () => {
const event = makeEvent([], ["result1"]);
handleConsoleEvent(event, { logToFile: false }, shardBuffers, state);
expect(core.info).toHaveBeenCalledWith("result1");
});
it("calls core.error when error field is non-empty", () => {
const event = makeEvent([], [], "Script crashed");
handleConsoleEvent(event, { logToFile: false }, shardBuffers, state);
expect(core.error).toHaveBeenCalledWith("Script crashed");
expect(state.sawErrorLog).toBe(true);
});
it("sets state.sawTraceback when error contains stack frames", () => {
const error = "TypeError: boom\n at Object.<anonymous> (main:1:1)";
const event = makeEvent([], [], error);
handleConsoleEvent(event, { logToFile: false }, shardBuffers, state);
expect(state.sawTraceback).toBe(true);
expect(state.sawErrorLog).toBe(true);
});
it("does not set sawTraceback for plain error without stack frames", () => {
const event = makeEvent([], [], "Script error: low CPU");
handleConsoleEvent(event, { logToFile: false }, shardBuffers, state);
expect(state.sawTraceback).toBe(false);
expect(state.sawErrorLog).toBe(true);
});
it("sets state.sawWarningLog and calls core.warning for warn lines", () => {
const event = makeEvent(["<font color='orange'>low energy</font>"]);
handleConsoleEvent(event, { logToFile: false }, shardBuffers, state);
expect(state.sawWarningLog).toBe(true);
expect(core.warning).toHaveBeenCalled();
});
it("calls core.warning regardless of logToFile setting", () => {
const event = makeEvent(["<font color='orange'>warn line</font>"]);
handleConsoleEvent(event, { logToFile: true }, shardBuffers, state);
expect(core.warning).toHaveBeenCalled();
});
it("handles missing messages gracefully (no crash on empty event)", () => {
const event = { data: {} };
expect(() =>
handleConsoleEvent(event, { logToFile: false }, shardBuffers, state),
).not.toThrow();
});
it("handles completely empty event gracefully", () => {
const event = {};
expect(() =>
handleConsoleEvent(event, { logToFile: false }, shardBuffers, state),
).not.toThrow();
});
it("filters messages by shard when targetShard is provided", () => {
const event = { data: { shard: "shard0", messages: { log: ["msg0"] } } };
handleConsoleEvent(event, { shard: "shard1" }, shardBuffers, state);
expect(core.info).not.toHaveBeenCalled();
handleConsoleEvent(event, { shard: "shard0" }, shardBuffers, state);
expect(core.info).toHaveBeenCalledWith("[shard0] msg0");
});
it("does not filter when targetShard is undefined", () => {
const event = { data: { shard: "shard0", messages: { log: ["msg0"] } } };
handleConsoleEvent(event, {}, shardBuffers, state);
expect(core.info).toHaveBeenCalledWith("[shard0] msg0");
});
it("buffers messages separately for different shards", () => {
const event0 = { data: { shard: "shard0", messages: { log: ["msg0"] } } };
const event1 = { data: { shard: "shard1", messages: { log: ["msg1"] } } };
handleConsoleEvent(event0, { logToFile: true }, shardBuffers, state);
handleConsoleEvent(event1, { logToFile: true }, shardBuffers, state);
expect(shardBuffers["shard0"]).toEqual(["msg0"]);
expect(shardBuffers["shard1"]).toEqual(["msg1"]);
});
it("uses 'default' key when shard is missing in event", () => {
const event = { data: { messages: { log: ["msg"] } } };
handleConsoleEvent(event, { logToFile: true }, shardBuffers, state);
expect(shardBuffers["default"]).toEqual(["msg"]);
});
});
// ────────────────────────────────────────────────────────────────────────────
// writeLogFile
// ────────────────────────────────────────────────────────────────────────────
describe("writeLogFile", () => {
let tempDir;
let tempFile;
beforeEach(async () => {
tempDir = await fs.promises.mkdtemp(
path.join(os.tmpdir(), "monitor-test-"),
);
tempFile = path.join(tempDir, "log.txt");
});
afterEach(async () => {
await fs.promises.rm(tempDir, { recursive: true, force: true });
});
it("writes all lines to file joined by newlines", async () => {
await writeLogFile(["line1", "line2", "line3"], tempFile);
const content = await fs.promises.readFile(tempFile, "utf8");
expect(content).toBe("line1\nline2\nline3");
});
it("writes an empty file when given an empty array", async () => {
await writeLogFile([], tempFile);
const content = await fs.promises.readFile(tempFile, "utf8");
expect(content).toBe("");
});
});
// ────────────────────────────────────────────────────────────────────────────
// monitorConsole — integration with mocked API
// ────────────────────────────────────────────────────────────────────────────
/**
* Builds a mock ScreepsAPI that:
* - api.time(shard) returns ticks from a list on each call
* - api.socket.connect() → resolves immediately
* - api.socket.subscribe() → resolves immediately (stores the callback)
* - api.socket.disconnect()→ no-op
*/
function buildMockApi({
ticks = [100, 101, 102, 103, 104, 105],
hostname = "builder64",
} = {}) {
let tickIndex = 0;
let consoleCallback = null;
const socket = {
connect: vi.fn().mockResolvedValue(undefined),
subscribe: vi.fn().mockImplementation((_path, cb) => {
consoleCallback = cb;
return Promise.resolve();
}),
disconnect: vi.fn(),
};
const api = {
opts: { hostname },
time: vi.fn().mockImplementation(() => {
const t = ticks[Math.min(tickIndex, ticks.length - 1)];
tickIndex++;
return Promise.resolve({ time: t });
}),
socket,
// Expose so tests can fire console events
_fireConsole: (eventData) => {
if (consoleCallback) {
consoleCallback({ data: eventData });
}
},
};
return api;
}
const BASE_OPTS = {
monitor: 3,
logToFile: false,
onTraceback: "fail",
onErrorLog: "warn",
onWarningLog: "ignore",
monitorInterval: 2,
hostname: "builder64",
};
describe("monitorConsole", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("returns all three flags false on a clean run", async () => {
const api = buildMockApi({ ticks: [100, 101, 102, 103] });
const result = await monitorConsole(api, BASE_OPTS);
expect(result).toEqual({
sawTraceback: false,
sawErrorLog: false,
sawWarningLog: false,
});
});
it("calls api.socket.connect() exactly once", async () => {
const api = buildMockApi({ ticks: [100, 101, 102, 103] });
await monitorConsole(api, BASE_OPTS);
expect(api.socket.connect).toHaveBeenCalledTimes(1);
});
it("calls api.socket.disconnect() after completion", async () => {
const api = buildMockApi({ ticks: [100, 101, 102, 103] });
await monitorConsole(api, BASE_OPTS);
expect(api.socket.disconnect).toHaveBeenCalledTimes(1);
});
it("subscribes to 'console' for a private server", async () => {
const api = buildMockApi({
hostname: "builder64",
ticks: [100, 101, 102, 103],
});
await monitorConsole(api, { ...BASE_OPTS, hostname: "builder64" });
expect(api.socket.subscribe).toHaveBeenCalledWith(
"console",
expect.any(Function),
);
});
it("subscribes to 'console' for the official server (default)", async () => {
const api = buildMockApi({
hostname: "screeps.com",
ticks: [100, 101, 102, 103],
});
await monitorConsole(api, { ...BASE_OPTS, hostname: "screeps.com" });
expect(api.socket.subscribe).toHaveBeenCalledWith(
"console",
expect.any(Function),
);
});
it("subscribes to 'console' even when custom shard is provided", async () => {
const api = buildMockApi({
hostname: "screeps.com",
ticks: [100, 101, 102, 103],
});
await monitorConsole(api, {
...BASE_OPTS,
hostname: "screeps.com",
shard: "shard3",
});
expect(api.socket.subscribe).toHaveBeenCalledWith(
"console",
expect.any(Function),
);
// Verify polling still uses shard3 for timing
expect(api.time).toHaveBeenCalledWith("shard3");
});
it("returns sawTraceback=true when a traceback event arrives", async () => {
const api = buildMockApi({ ticks: [100, 101, 102, 103] });
// Schedule console event before poll advances
setTimeout(() => {
api._fireConsole({
messages: { log: [], results: [] },
error: "TypeError: boom\n at Object.<anonymous> (main:1:1)",
});
}, 0);
const result = await monitorConsole(api, BASE_OPTS);
expect(result.sawTraceback).toBe(true);
});
it("returns sawErrorLog=true when an error (no traceback) event arrives", async () => {
const api = buildMockApi({ ticks: [100, 101, 102, 103] });
setTimeout(() => {
api._fireConsole({
messages: { log: [], results: [] },
error: "Script error: CPU limit exceeded",
});
}, 0);
const result = await monitorConsole(api, BASE_OPTS);
expect(result.sawErrorLog).toBe(true);
expect(result.sawTraceback).toBe(false);
});
it("returns sawWarningLog=true when a warn log line arrives", async () => {
const api = buildMockApi({ ticks: [100, 101, 102, 103] });
setTimeout(() => {
api._fireConsole({
messages: {
log: ["<font color='orange'>low energy</font>"],
results: [],
},
error: null,
});
}, 0);
const result = await monitorConsole(api, BASE_OPTS);
expect(result.sawWarningLog).toBe(true);
});
it("logs progress via core.info at monitorInterval boundaries when logToFile=true", async () => {
// ticks: start=100, then 101,102(interval),103(done at delta=3)
const api = buildMockApi({ ticks: [100, 100, 101, 102, 103] });
await monitorConsole(api, {
...BASE_OPTS,
logToFile: true,
monitorInterval: 2,
});
// Progress should be logged when elapsed reaches 2
const infoCalls = core.info.mock.calls.map((c) => c[0]);
expect(infoCalls.some((m) => m.includes("2/3"))).toBe(true);
});
it("calls api.socket.disconnect() even if an error occurs during polling", async () => {
const api = buildMockApi({ ticks: [100] });
// Make time() reject after first call
let calls = 0;
api.time = vi.fn().mockImplementation(() => {
calls++;
if (calls > 1) return Promise.reject(new Error("network error"));
return Promise.resolve({ time: 100 });
});
await expect(monitorConsole(api, BASE_OPTS)).rejects.toThrow(
"network error",
);
expect(api.socket.disconnect).toHaveBeenCalledTimes(1);
});
it("exits early if a traceback occurs and onTraceback='fail'", async () => {
// startTick=100, monitor=10 ticks.
// If it didn't exit early, it would call api.time() many times.
const api = buildMockApi({
ticks: [100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110],
});
// Fire a traceback event after the first poll
setTimeout(() => {
api._fireConsole({
messages: { log: [], results: [] },
error:
"TypeError: fail-fast test\n at Object.<anonymous> (main:1:1)",
});
}, 100);
const result = await monitorConsole(api, {
...BASE_OPTS,
monitor: 10,
onTraceback: "fail",
});
expect(result.sawTraceback).toBe(true);
// We expect it to have called api.time fewer than 10 times (excluding the startTick call)
expect(api.time.mock.calls.length).toBeLessThan(10);
});
it("detects encoded tracebacks in log lines", async () => {
const api = buildMockApi({ ticks: [100, 101, 102] });
setTimeout(() => {
api._fireConsole({
messages: {
log: [
"Error: ReferenceError: a is not defined%0A at eval (eval at <anonymous> (_console1778948572008_0:1:46), <anonymous>:1:1)%0A at _console1778948572008_0:1:46",
],
results: [],
},
error: null,
});
}, 50);
const result = await monitorConsole(api, {
...BASE_OPTS,
onTraceback: "fail",
});
expect(result.sawTraceback).toBe(true);
expect(result.sawErrorLog).toBe(true);
});
it("creates separate log files for different shards when logToFile=true", async () => {
const api = buildMockApi({ ticks: [100, 101, 102, 103] });
setTimeout(() => {
api._fireConsole({
shard: "shard0",
messages: { log: ["msg0"], results: [] },
error: null,
});
api._fireConsole({
shard: "shard1",
messages: { log: ["msg1"], results: [] },
error: null,
});
}, 50);
// Verify uploadArtifact was called with two files
await monitorConsole(api, {
...BASE_OPTS,
logToFile: true,
});
expect(artifact.create().uploadArtifact).toHaveBeenCalledWith(
"screeps-console-log",
expect.arrayContaining([
expect.stringContaining("shard0_console_log.txt"),
expect.stringContaining("shard1_console_log.txt"),
]),
expect.any(String),
{ continueOnError: true },
);
});
});
+43 -2
View File
@@ -37,6 +37,47 @@ inputs:
description: 'Branch in Screeps to which the code will be uploaded (default: default).'
required: false
default: default
git-replace:
description: Allows for the overwrite of the "{{gitRef}}", "{{gitHash}}" and "{{deployTime}}" values in the file matching the file pattern. The file pattern will be combined with the prefix.
required: false
shard:
description: The Screeps shard to monitor (e.g. shard0, shard1). Defaults to shard0 on the official server.
required: false
monitor:
description: Number of game ticks to monitor the Screeps console after deploying (0 = disabled).
required: false
default: '0'
log_to_file:
description: 'Buffer stdout to an artifact file instead of streaming live (default: false). Errors/warnings always stream live.'
required: false
default: 'false'
on_traceback:
description: 'Action on JS traceback detection: ignore, warn, or fail (default: fail).'
required: false
default: fail
on_error_log:
description: 'Action on Screeps error-console output: ignore, warn, or fail (default: warn).'
required: false
default: warn
on_warning_log:
description: 'Action on console.warn output: ignore, warn, or fail (default: ignore).'
required: false
default: ignore
monitor_interval:
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.
saw_error_log:
description: true if the Screeps error console had output during monitoring.
saw_warning_log:
description: true if console.warn output was detected during monitoring.
runs:
using: node12
main: index.js
using: node20
main: dist/index.js
+27
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
+207 -36
View File
@@ -1,8 +1,59 @@
const { ScreepsAPI } = require("screeps-api");
const core = require("@actions/core");
const fs = require("fs");
const glob = require("glob");
const path = require("path");
import { ScreepsAPI } from "screeps-api";
import * as core from "@actions/core";
import fs from "fs";
import { glob } from "glob";
import path from "path";
import { fileURLToPath } from "url";
import { monitorConsole } from "./monitor.js";
/**
* Replaces specific placeholder strings within the provided content with corresponding dynamic values.
*
* This function specifically targets three placeholders:
* - {{gitHash}} is replaced with the current Git commit hash, obtained from the GITHUB_SHA environment variable.
* - {{gitRef}} is replaced with the Git reference (branch or tag) that triggered the workflow, obtained from the GITHUB_REF environment variable.
* - {{deployTime}} is replaced with the current ISO timestamp.
*
* Note: This function is designed for use within a GitHub Actions workflow where GITHUB_SHA and GITHUB_REF environment variables are automatically set.
*
* @param {string} content - The string content in which placeholders are to be replaced.
* @returns {string} The content with placeholders replaced by their respective dynamic values.
*/
export function replacePlaceholders(content, hostname) {
const deployTime = new Date().toISOString();
return content
.replace(/{{gitHash}}/g, process.env.GITHUB_SHA)
.replace(/{{gitRef}}/g, process.env.GITHUB_REF)
.replace(/{{deployTime}}/g, deployTime)
.replace(/{{hostname}}/g, hostname);
}
/**
* Reads all files matching a specified pattern, replaces certain placeholders in their content, and writes the updated content back to the files.
*
* This function searches for files in the filesystem using the provided glob pattern, optionally prefixed. It reads each file,
* uses the `replacePlaceholders` function to replace specific placeholders in the file's content, and then writes the modified content
* back to the original file. This is useful for dynamically updating file contents in a batch process, such as during a build or deployment.
*
* @param {string} pattern - The glob pattern used to find files. Example: '*.js' for all JavaScript files.
* @param {string} [prefix] - An optional directory prefix to prepend to the glob pattern. This allows searching within a specific directory.
* @returns {Promise<string[]>} A promise that resolves with an array of file paths that were processed, or rejects with an error if the process fails.
*/
export async function readReplaceAndWriteFiles(pattern, prefix, hostname) {
let globPattern = prefix ? path.join(prefix, pattern) : pattern;
globPattern = globPattern.replace(/\\/g, "/");
const files = await glob(globPattern);
let processPromises = files.map((file) => {
return fs.promises.readFile(file, "utf8").then((content) => {
content = replacePlaceholders(content, hostname);
return fs.promises.writeFile(file, content);
});
});
await Promise.all(processPromises);
return files;
}
/**
* Reads files matching a glob pattern into a dictionary.
@@ -10,21 +61,15 @@ const path = require("path");
* @param {string} prefix - Directory prefix for file paths.
* @returns {Promise<Object>} - Promise resolving to a dictionary of file contents keyed by filenames.
*/
function readFilesIntoDict(pattern, prefix) {
export async function readFilesIntoDict(pattern, prefix) {
// Prepend the prefix to the glob pattern
const globPattern = prefix ? path.join(prefix, pattern) : pattern;
return new Promise((resolve, reject) => {
glob(globPattern, (err, files) => {
if (err) {
return reject(err);
}
let globPattern = prefix ? path.join(prefix, pattern) : pattern;
globPattern = globPattern.replace(/\\/g, "/");
const files = await glob(globPattern);
let fileDict = {};
let readPromises = [];
files.forEach((file) => {
let readPromise = fs.promises.readFile(file, "utf8").then((content) => {
let readPromises = files.map((file) => {
return fs.promises.readFile(file, "utf8").then((content) => {
// Remove the prefix from the filename and drop the file suffix
let key = file;
if (prefix && file.startsWith(prefix)) {
@@ -34,16 +79,10 @@ function readFilesIntoDict(pattern, prefix) {
fileDict[key] = content;
});
readPromises.push(readPromise);
});
// Use Promise.all to ensure all files are read before resolving
Promise.all(readPromises)
.then(() => resolve(fileDict))
.catch(reject);
});
});
await Promise.all(readPromises);
return fileDict;
}
/**
@@ -53,7 +92,7 @@ function readFilesIntoDict(pattern, prefix) {
* @param {string} password - The password.
* @returns {string|null} - Returns an error message if validation fails, otherwise null.
*/
function validateAuthentication(token, username, password) {
export function validateAuthentication(token, username, password) {
if (token) {
if (username || password) {
return "Token is defined along with username and/or password.";
@@ -72,10 +111,32 @@ function validateAuthentication(token, username, password) {
return null; // No errors found
}
/**
* Applies the 'ignore' | 'warn' | 'fail' enum action when the given flag is true.
* Exported so it can be unit-tested independently.
*
* @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 false;
if (action === "warn") {
core.warning(message);
return false;
}
if (action === "fail") {
core.setFailed(message);
return true;
}
return false;
}
/**
* Posts code to Screeps server.
*/
async function postCode() {
export async function postCode() {
const protocol = core.getInput("protocol") || "https";
const hostname = core.getInput("hostname") || "screeps.com";
const port = core.getInput("port") || "443";
@@ -88,14 +149,19 @@ async function postCode() {
const pattern = core.getInput("pattern") || "*.js";
const branch = core.getInput("branch") || "default";
const gitReplace = core.getInput("git-replace") || null;
if (gitReplace) {
await readReplaceAndWriteFiles(gitReplace, prefix, hostname);
}
const files_to_push = await readFilesIntoDict(pattern, prefix);
core.info(`Trying to upload the following files to ${branch}:`);
Object.keys(files_to_push).forEach((key) => {
core.info(`Key: ${key}`);
});
core.info(files_to_push);
const login_arguments = {
token: token,
username: username,
@@ -106,20 +172,125 @@ async function postCode() {
path: path,
};
core.info(`Trying to upload the following files to ${branch}:`);
Object.keys(login_arguments).forEach((key) => {
core.info(`Key: ${key}`);
});
core.info("login_arguments:");
core.info(JSON.stringify(login_arguments, null, 2));
const errorMessage = validateAuthentication(token, username, password);
if (errorMessage) {
core.error(errorMessage);
return;
}
let api = new ScreepsAPI(login_arguments);
const api = new ScreepsAPI(login_arguments);
const response = await api.code.set(branch, files_to_push);
core.info(JSON.stringify(response, null, 2));
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}`);
} 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,
onErrorLog,
onWarningLog,
monitorInterval: parseInt(core.getInput("monitor_interval") || "10", 10),
hostname,
shard: core.getInput("shard") || undefined,
});
core.setOutput("saw_traceback", String(result.sawTraceback));
core.setOutput("saw_error_log", String(result.sawErrorLog));
core.setOutput("saw_warning_log", String(result.sawWarningLog));
const fail1 = applyOnAction(
onTraceback,
result.sawTraceback,
"Screeps console: traceback detected",
);
const fail2 = applyOnAction(
onErrorLog,
result.sawErrorLog,
"Screeps console: error log output detected",
);
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}`);
}
}
}
}
const __filename = fileURLToPath(import.meta.url);
if (process.argv[1] === __filename) {
postCode();
}
+420
View File
@@ -0,0 +1,420 @@
import * as core from "@actions/core";
import { create } from "@actions/artifact";
import fs from "fs";
import path from "path";
import os from "os";
/**
* Returns true if the hostname is the official Screeps server.
* Used to decide whether to prefix the subscribe path with a shard name.
*
* @param {string} hostname - e.g. "screeps.com" or "builder64"
* @returns {boolean}
*/
export function isOfficialServer(hostname) {
return hostname === "screeps.com";
}
/**
* Builds the channel path argument passed to socket.subscribe().
*
* The screeps-api socket automatically prefixes `user:<id>/` when the path
* does not match the `type:id` pattern, so we only supply the channel part:
* If shard is provided → "<shard>/console"
* Official server → "shard0/console" (if no shard provided)
* Private server → "console" (if no shard provided)
*
* @param {string} hostname
* @param {string} [shard]
* @returns {string}
*/
export function buildSubscribePath(hostname, shard) {
// The console channel on Screeps official and most private servers is 'console'.
// We subscribe to the aggregate feed and filter by shard in handleConsoleEvent.
return "console";
}
/**
* Returns true when errorText contains JavaScript stack-frame lines.
* Screeps places runtime errors (including stack traces) in event.data.error.
* A traceback is identified by lines beginning with four spaces followed by "at ".
*
* @param {string|null|undefined} errorText - Contents of event.data.error
* @returns {boolean}
*/
export function detectTraceback(errorText) {
if (!errorText) return false;
const text = safeDecode(errorText);
return /^\s{4}at /m.test(text);
}
/**
* Returns true when any log line contains Screeps console.warn markup.
* Screeps wraps console.warn() output in orange or yellow <font> HTML tags.
*
* @param {string[]|null|undefined} logLines - Contents of event.data.messages.log
* @returns {boolean}
*/
export function detectWarning(logLines) {
if (!logLines || logLines.length === 0) return false;
const warnPattern = /<font\s+color=['"](?:orange|yellow)['"]/i;
return logLines.some((line) => warnPattern.test(line));
}
/**
* Safely decodes a URI-encoded string from the Screeps console.
* Returns the original string if decoding fails or if no '%' is present.
*
* @param {string} text
* @returns {string}
*/
export function safeDecode(text) {
if (typeof text !== "string") return text;
let result = text;
if (result.includes("%")) {
try {
result = decodeURIComponent(result);
} catch (err) {
// Ignore decoding errors
}
}
// Screeps console often contains HTML entities
return result
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&amp;/g, "&");
}
/**
* Outputs text to the action log, splitting by newline to ensure proper display.
* Optionally prepends a [shard] prefix.
*
* @param {string} text
* @param {"info"|"warning"|"error"} level
* @param {string} [shard]
*/
export function outputMultiline(text, level = "info", shard = null) {
if (!text) return;
const prefix = shard ? `[${shard}] ` : "";
const lines = text.split(/\r?\n/);
lines.forEach((line) => {
const formattedLine = `${prefix}${line}`;
if (level === "error") core.error(formattedLine);
else if (level === "warning") core.warning(formattedLine);
else core.info(formattedLine);
});
}
/**
* Builds a CI-friendly progress string for core.info().
*
* @param {number} elapsed - Ticks elapsed since monitoring started
* @param {number} total - Total ticks to monitor
* @returns {string} - e.g. "[Monitor] 10/50 ticks elapsed..."
*/
export function buildProgressMessage(elapsed, total) {
return `[Monitor] ${elapsed}/${total} ticks elapsed...`;
}
/**
* Writes an array of log lines to a UTF-8 text file, one line per entry.
*
* @param {string[]} lines - Lines to write
* @param {string} filePath - Absolute path to write to
* @returns {Promise<void>}
*/
export async function writeLogFile(lines, filePath) {
await fs.promises.writeFile(filePath, lines.join("\n"), "utf8");
}
/**
* Uploads one or more files as a named workflow artifact using @actions/artifact.
* Degrades gracefully to core.warning() if the runner does not support the
* artifact service.
*
* @param {string[]} filePaths - Absolute paths of the files to upload
* @param {string} [artifactName="screeps-console-log"] - Artifact display name
* @returns {Promise<void>}
*/
export async function uploadLogArtifacts(
filePaths,
artifactName = "screeps-console-log",
) {
if (!filePaths || filePaths.length === 0) return;
try {
const client = create();
const rootDir = path.dirname(filePaths[0]);
await client.uploadArtifact(artifactName, filePaths, rootDir, {
continueOnError: true,
});
core.info(`[Monitor] Console logs uploaded as artifact '${artifactName}'.`);
} catch (err) {
core.warning(
`[Monitor] Could not upload console logs as artifact: ${err.message}`,
);
}
}
/**
* Processes a single 'console' WebSocket event from the Screeps socket.
* Mutates `state` and `stdoutBuffer` in place; never throws.
*
* WebSocket event.data shape:
* {
* messages: {
* log: string[], // stdout (console.warn included with HTML markup)
* results: string[], // return values of console-evaluated expressions
* },
* error: string | null, // stderr, runtime errors, tracebacks
* }
*
* Behaviour:
* - Warn lines (orange/yellow <font> tags) → always core.warning() (live),
* sets state.sawWarningLog, still included in stdoutBuffer / core.info().
* - Error field → always core.error() (live), sets state.sawErrorLog.
* If a stack frame is detected → also sets state.sawTraceback.
* - All stdout lines → core.info() when logToFile=false,
* pushed to stdoutBuffer when logToFile=true.
*
* @param {{ data?: { messages?: { log?: string[], results?: string[] }, error?: string, shard?: string } }} event
* @param {{ logToFile: boolean, shard?: string }} opts
* @param {Record<string, string[]>} shardBuffers - Mutable buffer Map used in logToFile mode
* @param {{ sawTraceback: boolean, sawErrorLog: boolean, sawWarningLog: boolean }} state
*/
export function handleConsoleEvent(event, opts, shardBuffers, state) {
const { logToFile, shard: targetShard } = opts;
const data = event?.data ?? {};
// Shard filtering: If a shard is specified in opts, only process messages from that shard.
// Official server events include a 'shard' property in event.data.
if (targetShard && data.shard && data.shard !== targetShard) {
return;
}
const logLines = data?.messages?.log ?? [];
const results = data?.messages?.results ?? [];
const errorText = data?.error ?? null;
// Warn detection (always live regardless of logToFile)
if (detectWarning(logLines)) {
state.sawWarningLog = true;
const warnPattern = /<font\s+color=['"](?:orange|yellow)['"]/i;
logLines
.filter((l) => warnPattern.test(l))
.forEach((l) => outputMultiline(safeDecode(l), "warning", data.shard));
}
// Traceback detection in log lines (Screeps sometimes sends errors here)
if (logLines.some((l) => detectTraceback(l))) {
state.sawTraceback = true;
state.sawErrorLog = true;
}
// Stdout lines
const allStdout = [...logLines, ...results].map(safeDecode);
if (allStdout.length > 0) {
if (logToFile) {
const shard = data.shard || "default";
if (!shardBuffers[shard]) shardBuffers[shard] = [];
shardBuffers[shard].push(...allStdout);
} else {
allStdout.forEach((l) => outputMultiline(l, "info", data.shard));
}
}
// Error field (always live)
if (errorText) {
state.sawErrorLog = true;
const decodedError = safeDecode(errorText);
outputMultiline(decodedError, "error", data.shard);
if (detectTraceback(decodedError)) {
state.sawTraceback = true;
}
}
}
/**
* Sleeps for the given number of milliseconds.
*
* @param {number} ms
* @returns {Promise<void>}
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Polls GET /api/game/time every `intervalMs` milliseconds until the tick
* delta (currentTick - startTick) reaches or exceeds `targetTicks`.
* Calls `onProgress(elapsed, targetTicks)` on every poll so the caller can
* log progress at whatever cadence it chooses.
*
* @param {import('screeps-api').ScreepsAPI} api
* @param {number} startTick - Tick number recorded before monitoring started
* @param {number} targetTicks - Stop when (currentTick - startTick) >= this
* @param {string|undefined} shard - "shard0" for official, undefined for private
* @param {number} intervalMs - Poll interval in milliseconds
* @param {(elapsed: number, total: number) => void} onProgress
* @returns {Promise<number>} Final elapsed tick count
*/
export async function pollUntilDone(
api,
startTick,
targetTicks,
shard,
intervalMs,
onProgress,
shouldStop = () => false,
) {
let elapsed = 0;
while (elapsed < targetTicks && !shouldStop()) {
await sleep(intervalMs);
const { time } = await api.time(shard);
elapsed = time - startTick;
onProgress(elapsed, targetTicks);
}
return elapsed;
}
/**
* @typedef {Object} MonitorOptions
* @property {number} monitor - Number of game ticks to collect.
* @property {boolean} logToFile - Buffer stdout to artifact instead of streaming.
* @property {'ignore'|'warn'|'fail'} onTraceback - Action on traceback detection.
* @property {'ignore'|'warn'|'fail'} onErrorLog - Action on any error-console output.
* @property {'ignore'|'warn'|'fail'} onWarningLog - Action on console.warn output.
* @property {number} monitorInterval - Print a progress update every N ticks.
* @property {string} hostname - Screeps hostname (for shard derivation).
* @property {string} [shard] - Optional shard to monitor.
*/
/**
* @typedef {Object} MonitorResult
* @property {boolean} sawTraceback - True if a JS stack trace was detected.
* @property {boolean} sawErrorLog - True if the error console had any output.
* @property {boolean} sawWarningLog - True if console.warn output was detected.
*/
/**
* Monitors the Screeps console for a given number of game ticks after a deploy.
*
* Flow:
* 1. Fetch startTick via GET /api/game/time (REST poll).
* 2. Connect WebSocket and subscribe to the console channel.
* 3. Run the tick-poll loop (500 ms interval) concurrently with the socket
* event listener; the poll loop drives the stop condition.
* 4. On each 'console' WebSocket event, delegate to handleConsoleEvent().
* 5. When poll finishes, disconnect socket cleanly (in a finally block).
* 6. If logToFile=true: write buffered stdout to a temp file and upload artifact.
* 7. Return MonitorResult.
*
* @param {import('screeps-api').ScreepsAPI} api
* @param {MonitorOptions} opts
* @returns {Promise<MonitorResult>}
*/
export async function monitorConsole(api, opts) {
const {
monitor,
logToFile,
monitorInterval,
hostname,
shard: providedShard,
} = opts;
// Use provided shard, or fall back to shard0 for official, or undefined for private
const shard =
providedShard || (isOfficialServer(hostname) ? "shard0" : undefined);
const subscribePath = buildSubscribePath(hostname, providedShard);
// Shared mutable state — updated by handleConsoleEvent via event listener
const shardBuffers = {}; // { [shardName]: string[] }
const state = {
sawTraceback: false,
sawErrorLog: false,
sawWarningLog: false,
};
let lastProgressTick = 0;
// Step 1: record starting tick
const { time: startTick } = await api.time(shard);
// Step 2: connect socket + subscribe
await api.socket.connect();
await api.socket.subscribe(subscribePath, (event) => {
handleConsoleEvent(event, opts, shardBuffers, state);
});
core.info(
`[Monitor] Watching Screeps console for ${monitor} ticks` +
(shard ? ` on ${shard}` : "") +
"...",
);
// Step 3 & 4: tick-poll loop
try {
await pollUntilDone(
api,
startTick,
monitor,
shard,
500,
(elapsed, total) => {
// Print progress at configured interval boundaries
if (
elapsed > 0 &&
elapsed >= lastProgressTick + monitorInterval &&
elapsed <= total
) {
core.info(buildProgressMessage(elapsed, total));
lastProgressTick = elapsed;
}
},
() => {
// Fail-fast logic: stop monitoring if any 'fail' action is triggered
if (opts.onTraceback === "fail" && state.sawTraceback) return true;
if (opts.onErrorLog === "fail" && state.sawErrorLog) return true;
if (opts.onWarningLog === "fail" && state.sawWarningLog) return true;
return false;
},
);
} finally {
// Step 5: always disconnect cleanly
api.socket.disconnect();
}
// Step 6: artifact upload
const shardKeys = Object.keys(shardBuffers);
if (logToFile && shardKeys.length > 0) {
const tmpDir = await fs.promises.mkdtemp(
path.join(os.tmpdir(), "screeps-monitor-"),
);
const filePaths = [];
for (const sName of shardKeys) {
const fileName =
sName === "default"
? "screeps_console_log.txt"
: `${sName}_console_log.txt`;
const tmpFile = path.join(tmpDir, fileName);
await writeLogFile(shardBuffers[sName], tmpFile);
filePaths.push(tmpFile);
}
await uploadLogArtifacts(filePaths);
} else if (logToFile) {
core.info(
"[Monitor] No stdout lines were collected; skipping artifact upload.",
);
}
core.info(
`[Monitor] Done. sawTraceback=${state.sawTraceback} sawErrorLog=${state.sawErrorLog} sawWarningLog=${state.sawWarningLog}`,
);
return {
sawTraceback: state.sawTraceback,
sawErrorLog: state.sawErrorLog,
sawWarningLog: state.sawWarningLog,
};
}
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../detect-libc/bin/detect-libc.js" "$@"
else
exec node "$basedir/../detect-libc/bin/detect-libc.js" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\detect-libc\bin\detect-libc.js" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
} else {
& "$basedir/node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
} else {
& "node$exe" "$basedir/../detect-libc/bin/detect-libc.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../yamljs/bin/json2yaml" "$@"
else
exec node "$basedir/../yamljs/bin/json2yaml" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\yamljs\bin\json2yaml" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../yamljs/bin/json2yaml" $args
} else {
& "$basedir/node$exe" "$basedir/../yamljs/bin/json2yaml" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../yamljs/bin/json2yaml" $args
} else {
& "node$exe" "$basedir/../yamljs/bin/json2yaml" $args
}
$ret=$LASTEXITCODE
}
exit $ret
Generated Vendored
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@"
else
exec node "$basedir/../mkdirp/bin/cmd.js" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\mkdirp\bin\cmd.js" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
} else {
& "$basedir/node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
} else {
& "node$exe" "$basedir/../mkdirp/bin/cmd.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../prebuild-install/bin.js" "$@"
else
exec node "$basedir/../prebuild-install/bin.js" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\prebuild-install\bin.js" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../prebuild-install/bin.js" $args
} else {
& "$basedir/node$exe" "$basedir/../prebuild-install/bin.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../prebuild-install/bin.js" $args
} else {
& "node$exe" "$basedir/../prebuild-install/bin.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
Generated Vendored
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../rc/cli.js" "$@"
else
exec node "$basedir/../rc/cli.js" "$@"
fi
Generated Vendored
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rc\cli.js" %*
Generated Vendored
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../rc/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../rc/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../rc/cli.js" $args
} else {
& "node$exe" "$basedir/../rc/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../screeps-api/bin/screeps-api.js" "$@"
else
exec node "$basedir/../screeps-api/bin/screeps-api.js" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\screeps-api\bin\screeps-api.js" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../screeps-api/bin/screeps-api.js" $args
} else {
& "$basedir/node$exe" "$basedir/../screeps-api/bin/screeps-api.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../screeps-api/bin/screeps-api.js" $args
} else {
& "node$exe" "$basedir/../screeps-api/bin/screeps-api.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret
Generated Vendored
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../semver/bin/semver" "$@"
else
exec node "$basedir/../semver/bin/semver" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\semver\bin\semver" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../semver/bin/semver" $args
} else {
& "$basedir/node$exe" "$basedir/../semver/bin/semver" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../semver/bin/semver" $args
} else {
& "node$exe" "$basedir/../semver/bin/semver" $args
}
$ret=$LASTEXITCODE
}
exit $ret
Generated Vendored
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../uuid/dist/bin/uuid" "$@"
else
exec node "$basedir/../uuid/dist/bin/uuid" "$@"
fi
Generated Vendored
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\uuid\dist\bin\uuid" %*
Generated Vendored
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../uuid/dist/bin/uuid" $args
} else {
& "$basedir/node$exe" "$basedir/../uuid/dist/bin/uuid" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../uuid/dist/bin/uuid" $args
} else {
& "node$exe" "$basedir/../uuid/dist/bin/uuid" $args
}
$ret=$LASTEXITCODE
}
exit $ret
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../yamljs/bin/yaml2json" "$@"
else
exec node "$basedir/../yamljs/bin/yaml2json" "$@"
fi
-17
View File
@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\yamljs\bin\yaml2json" %*
-28
View File
@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../yamljs/bin/yaml2json" $args
} else {
& "$basedir/node$exe" "$basedir/../yamljs/bin/yaml2json" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../yamljs/bin/yaml2json" $args
} else {
& "node$exe" "$basedir/../yamljs/bin/yaml2json" $args
}
$ret=$LASTEXITCODE
}
exit $ret
-844
View File
@@ -1,844 +0,0 @@
{
"name": "screeps-deploy-action",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@actions/core": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.1.tgz",
"integrity": "sha512-3lBR9EDAY+iYIpTnTIXmWcNbX3T2kCkAEQGIQx4NVQ0575nk2k3GRZDTPQG+vVtS2izSLmINlxXf0uLtnrTP+g==",
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
}
},
"node_modules/@actions/http-client": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz",
"integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==",
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
},
"node_modules/@fastify/busboy": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz",
"integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==",
"engines": {
"node": ">=14"
}
},
"node_modules/ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"optional": true
},
"node_modules/are-we-there-yet": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz",
"integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==",
"optional": true,
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/bindings": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz",
"integrity": "sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==",
"optional": true
},
"node_modules/bl": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz",
"integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==",
"optional": true,
"dependencies": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
}
},
"node_modules/bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/buffer-alloc": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
"integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
"optional": true,
"dependencies": {
"buffer-alloc-unsafe": "^1.1.0",
"buffer-fill": "^1.0.0"
}
},
"node_modules/buffer-alloc-unsafe": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
"integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
"optional": true
},
"node_modules/buffer-fill": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
"integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==",
"optional": true
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"optional": true
},
"node_modules/code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"optional": true
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"optional": true
},
"node_modules/decompress-response": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz",
"integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==",
"optional": true,
"dependencies": {
"mimic-response": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"optional": true,
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"optional": true
},
"node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"optional": true,
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"optional": true,
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/expand-template": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz",
"integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==",
"optional": true
},
"node_modules/fetch-ponyfill": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-6.1.1.tgz",
"integrity": "sha512-rWLgTr5A44/XhvCQPYj0X9Tc+cjUaHofSM4lcwjc9MavD5lkjIhJ+h8JQlavPlTIgDpwhuRozaIykBvX9ItaSA==",
"dependencies": {
"node-fetch": "~2.6.0"
}
},
"node_modules/fetch-ponyfill/node_modules/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"optional": true
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha512-14x4kjc6lkD3ltw589k0NrPD6cCNTD6CWoVUNpB85+DrtONoZn+Rug6xZU5RvSC4+TZPxA5AnBibQYAvZn41Hg==",
"optional": true,
"dependencies": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.0",
"object-assign": "^4.1.0",
"signal-exit": "^3.0.0",
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wide-align": "^1.1.0"
}
},
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"optional": true
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"optional": true
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"optional": true
},
"node_modules/is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
"optional": true,
"dependencies": {
"number-is-nan": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"optional": true
},
"node_modules/mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
"optional": true,
"engines": {
"node": ">=4"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"optional": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"optional": true,
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
"optional": true
},
"node_modules/node-abi": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.30.1.tgz",
"integrity": "sha512-/2D0wOQPgaUWzVSVgRMx+trKJRC2UG4SUc4oCJoXx9Uxjtp0Vy3/kt7zcbxHF8+Z/pK3UloLWzBISg72brfy1w==",
"optional": true,
"dependencies": {
"semver": "^5.4.1"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/noop-logger": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
"integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==",
"optional": true
},
"node_modules/npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true,
"dependencies": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
"gauge": "~2.7.3",
"set-blocking": "~2.0.0"
}
},
"node_modules/number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prebuild-install": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz",
"integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==",
"optional": true,
"dependencies": {
"detect-libc": "^1.0.3",
"expand-template": "^1.0.2",
"github-from-package": "0.0.0",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"node-abi": "^2.2.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"os-homedir": "^1.0.1",
"pump": "^2.0.1",
"rc": "^1.1.6",
"simple-get": "^2.7.0",
"tar-fs": "^1.13.0",
"tunnel-agent": "^0.6.0",
"which-pm-runs": "^1.0.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"optional": true
},
"node_modules/pump": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
"optional": true,
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"optional": true,
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"optional": true,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"optional": true
},
"node_modules/screeps-api": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/screeps-api/-/screeps-api-1.7.2.tgz",
"integrity": "sha512-41LW9gE9xQGWLi9aoRsUEND12ij0WSHTiFLdahfm3vS5HRpEpNOO7Vl96lO2Isuv0WVaete7Ctwx7nPJwEk8Vg==",
"dependencies": {
"bluebird": "^3.5.0",
"commander": "^2.15.1",
"fetch-ponyfill": "^6.0.2",
"node-fetch": "^2.1.2",
"ws": "^5.2.0",
"yamljs": "^0.3.0"
},
"bin": {
"screeps-api": "bin/screeps-api.js"
},
"optionalDependencies": {
"bufferutil": "^3.0.5",
"utf-8-validate": "^4.0.2"
}
},
"node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"optional": true,
"bin": {
"semver": "bin/semver"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"optional": true
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"optional": true
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true
},
"node_modules/simple-get": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.2.tgz",
"integrity": "sha512-Ijd/rV5o+mSBBs4F/x9oDPtTx9Zb6X9brmnXvMW4J7IR15ngi9q5xxqWBKU744jTZiaXtxaPL7uHG6vtN8kUkw==",
"optional": true,
"dependencies": {
"decompress-response": "^3.3.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"optional": true,
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
"optional": true,
"dependencies": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
"optional": true,
"dependencies": {
"ansi-regex": "^2.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tar-fs": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz",
"integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==",
"optional": true,
"dependencies": {
"chownr": "^1.0.1",
"mkdirp": "^0.5.1",
"pump": "^1.0.0",
"tar-stream": "^1.1.2"
}
},
"node_modules/tar-fs/node_modules/pump": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz",
"integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==",
"optional": true,
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/tar-stream": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz",
"integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==",
"optional": true,
"dependencies": {
"bl": "^1.0.0",
"buffer-alloc": "^1.2.0",
"end-of-stream": "^1.0.0",
"fs-constants": "^1.0.0",
"readable-stream": "^2.3.0",
"to-buffer": "^1.1.1",
"xtend": "^4.0.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/to-buffer": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz",
"integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==",
"optional": true
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"optional": true,
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/undici": {
"version": "5.28.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.0.tgz",
"integrity": "sha512-gM12DkXhlAc5+/TPe60iy9P6ETgVfqTuRJ6aQ4w8RYu0MqKuXhaq3/b86GfzDQnNA3NUO6aUNdvevrKH59D0Nw==",
"dependencies": {
"@fastify/busboy": "^2.0.0"
},
"engines": {
"node": ">=14.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"optional": true
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which-pm-runs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.1.0.tgz",
"integrity": "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==",
"optional": true,
"engines": {
"node": ">=4"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"optional": true,
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz",
"integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==",
"dependencies": {
"async-limiter": "~1.0.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"optional": true,
"engines": {
"node": ">=0.4"
}
},
"node_modules/yamljs": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz",
"integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==",
"dependencies": {
"argparse": "^1.0.7",
"glob": "^7.0.5"
},
"bin": {
"json2yaml": "bin/json2yaml",
"yaml2json": "bin/yaml2json"
}
}
}
}
-9
View File
@@ -1,9 +0,0 @@
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-335
View File
@@ -1,335 +0,0 @@
# `@actions/core`
> Core functions for setting results, logging, registering secrets and exporting variables across actions
## Usage
### Import the package
```js
// javascript
const core = require('@actions/core');
// typescript
import * as core from '@actions/core';
```
#### Inputs/Outputs
Action inputs can be read with `getInput` which returns a `string` or `getBooleanInput` which parses a boolean based on the [yaml 1.2 specification](https://yaml.org/spec/1.2/spec.html#id2804923). If `required` set to be false, the input should have a default value in `action.yml`.
Outputs can be set with `setOutput` which makes them available to be mapped into inputs of other actions to ensure they are decoupled.
```js
const myInput = core.getInput('inputName', { required: true });
const myBooleanInput = core.getBooleanInput('booleanInputName', { required: true });
const myMultilineInput = core.getMultilineInput('multilineInputName', { required: true });
core.setOutput('outputKey', 'outputVal');
```
#### Exporting variables
Since each step runs in a separate process, you can use `exportVariable` to add it to this step and future steps environment blocks.
```js
core.exportVariable('envVar', 'Val');
```
#### Setting a secret
Setting a secret registers the secret with the runner to ensure it is masked in logs.
```js
core.setSecret('myPassword');
```
#### PATH Manipulation
To make a tool's path available in the path for the remainder of the job (without altering the machine or containers state), use `addPath`. The runner will prepend the path given to the jobs PATH.
```js
core.addPath('/path/to/mytool');
```
#### Exit codes
You should use this library to set the failing exit code for your action. If status is not set and the script runs to completion, that will lead to a success.
```js
const core = require('@actions/core');
try {
// Do stuff
}
catch (err) {
// setFailed logs the message and sets a failing exit code
core.setFailed(`Action failed with error ${err}`);
}
```
Note that `setNeutral` is not yet implemented in actions V2 but equivalent functionality is being planned.
#### Logging
Finally, this library provides some utilities for logging. Note that debug logging is hidden from the logs by default. This behavior can be toggled by enabling the [Step Debug Logs](../../docs/action-debugging.md#step-debug-logs).
```js
const core = require('@actions/core');
const myInput = core.getInput('input');
try {
core.debug('Inside try block');
if (!myInput) {
core.warning('myInput was not set');
}
if (core.isDebug()) {
// curl -v https://github.com
} else {
// curl https://github.com
}
// Do stuff
core.info('Output to the actions build log')
core.notice('This is a message that will also emit an annotation')
}
catch (err) {
core.error(`Error ${err}, action may still succeed though`);
}
```
This library can also wrap chunks of output in foldable groups.
```js
const core = require('@actions/core')
// Manually wrap output
core.startGroup('Do some function')
doSomeFunction()
core.endGroup()
// Wrap an asynchronous function call
const result = await core.group('Do something async', async () => {
const response = await doSomeHTTPRequest()
return response
})
```
#### Annotations
This library has 3 methods that will produce [annotations](https://docs.github.com/en/rest/reference/checks#create-a-check-run).
```js
core.error('This is a bad error, action may still succeed though.')
core.warning('Something went wrong, but it\'s not bad enough to fail the build.')
core.notice('Something happened that you might want to know about.')
```
These will surface to the UI in the Actions page and on Pull Requests. They look something like this:
![Annotations Image](../../docs/assets/annotations.png)
These annotations can also be attached to particular lines and columns of your source files to show exactly where a problem is occuring.
These options are:
```typescript
export interface AnnotationProperties {
/**
* A title for the annotation.
*/
title?: string
/**
* The name of the file for which the annotation should be created.
*/
file?: string
/**
* The start line for the annotation.
*/
startLine?: number
/**
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
*/
endLine?: number
/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
*/
startColumn?: number
/**
* The end column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
* Defaults to `startColumn` when `startColumn` is provided.
*/
endColumn?: number
}
```
#### Styling output
Colored output is supported in the Action logs via standard [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). 3/4 bit, 8 bit and 24 bit colors are all supported.
Foreground colors:
```js
// 3/4 bit
core.info('\u001b[35mThis foreground will be magenta')
// 8 bit
core.info('\u001b[38;5;6mThis foreground will be cyan')
// 24 bit
core.info('\u001b[38;2;255;0;0mThis foreground will be bright red')
```
Background colors:
```js
// 3/4 bit
core.info('\u001b[43mThis background will be yellow');
// 8 bit
core.info('\u001b[48;5;6mThis background will be cyan')
// 24 bit
core.info('\u001b[48;2;255;0;0mThis background will be bright red')
```
Special styles:
```js
core.info('\u001b[1mBold text')
core.info('\u001b[3mItalic text')
core.info('\u001b[4mUnderlined text')
```
ANSI escape codes can be combined with one another:
```js
core.info('\u001b[31;46mRed foreground with a cyan background and \u001b[1mbold text at the end');
```
> Note: Escape codes reset at the start of each line
```js
core.info('\u001b[35mThis foreground will be magenta')
core.info('This foreground will reset to the default')
```
Manually typing escape codes can be a little difficult, but you can use third party modules such as [ansi-styles](https://github.com/chalk/ansi-styles).
```js
const style = require('ansi-styles');
core.info(style.color.ansi16m.hex('#abcdef') + 'Hello world!')
```
#### Action state
You can use this library to save state and get state for sharing information between a given wrapper action:
**action.yml**:
```yaml
name: 'Wrapper action sample'
inputs:
name:
default: 'GitHub'
runs:
using: 'node12'
main: 'main.js'
post: 'cleanup.js'
```
In action's `main.js`:
```js
const core = require('@actions/core');
core.saveState("pidToKill", 12345);
```
In action's `cleanup.js`:
```js
const core = require('@actions/core');
var pid = core.getState("pidToKill");
process.kill(pid);
```
#### OIDC Token
You can use these methods to interact with the GitHub OIDC provider and get a JWT ID token which would help to get access token from third party cloud providers.
**Method Name**: getIDToken()
**Inputs**
audience : optional
**Outputs**
A [JWT](https://jwt.io/) ID Token
In action's `main.ts`:
```js
const core = require('@actions/core');
async function getIDTokenAction(): Promise<void> {
const audience = core.getInput('audience', {required: false})
const id_token1 = await core.getIDToken() // ID Token with default audience
const id_token2 = await core.getIDToken(audience) // ID token with custom audience
// this id_token can be used to get access token from third party cloud providers
}
getIDTokenAction()
```
In action's `actions.yml`:
```yaml
name: 'GetIDToken'
description: 'Get ID token from Github OIDC provider'
inputs:
audience:
description: 'Audience for which the ID token is intended for'
required: false
outputs:
id_token1:
description: 'ID token obtained from OIDC provider'
id_token2:
description: 'ID token obtained from OIDC provider'
runs:
using: 'node12'
main: 'dist/index.js'
```
#### Filesystem path helpers
You can use these methods to manipulate file paths across operating systems.
The `toPosixPath` function converts input paths to Posix-style (Linux) paths.
The `toWin32Path` function converts input paths to Windows-style paths. These
functions work independently of the underlying runner operating system.
```js
toPosixPath('\\foo\\bar') // => /foo/bar
toWin32Path('/foo/bar') // => \foo\bar
```
The `toPlatformPath` function converts input paths to the expected value on the runner's operating system.
```js
// On a Windows runner.
toPlatformPath('/foo/bar') // => \foo\bar
// On a Linux runner.
toPlatformPath('\\foo\\bar') // => /foo/bar
```
-15
View File
@@ -1,15 +0,0 @@
export interface CommandProperties {
[key: string]: any;
}
/**
* Commands
*
* Command Format:
* ::name key=value,key=value::message
*
* Examples:
* ::warning::This is the message
* ::set-env name=MY_VAR::some value
*/
export declare function issueCommand(command: string, properties: CommandProperties, message: any): void;
export declare function issue(name: string, message?: string): void;
-92
View File
@@ -1,92 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.issue = exports.issueCommand = void 0;
const os = __importStar(require("os"));
const utils_1 = require("./utils");
/**
* Commands
*
* Command Format:
* ::name key=value,key=value::message
*
* Examples:
* ::warning::This is the message
* ::set-env name=MY_VAR::some value
*/
function issueCommand(command, properties, message) {
const cmd = new Command(command, properties, message);
process.stdout.write(cmd.toString() + os.EOL);
}
exports.issueCommand = issueCommand;
function issue(name, message = '') {
issueCommand(name, {}, message);
}
exports.issue = issue;
const CMD_STRING = '::';
class Command {
constructor(command, properties, message) {
if (!command) {
command = 'missing.command';
}
this.command = command;
this.properties = properties;
this.message = message;
}
toString() {
let cmdStr = CMD_STRING + this.command;
if (this.properties && Object.keys(this.properties).length > 0) {
cmdStr += ' ';
let first = true;
for (const key in this.properties) {
if (this.properties.hasOwnProperty(key)) {
const val = this.properties[key];
if (val) {
if (first) {
first = false;
}
else {
cmdStr += ',';
}
cmdStr += `${key}=${escapeProperty(val)}`;
}
}
}
}
cmdStr += `${CMD_STRING}${escapeData(this.message)}`;
return cmdStr;
}
}
function escapeData(s) {
return utils_1.toCommandValue(s)
.replace(/%/g, '%25')
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A');
}
function escapeProperty(s) {
return utils_1.toCommandValue(s)
.replace(/%/g, '%25')
.replace(/\r/g, '%0D')
.replace(/\n/g, '%0A')
.replace(/:/g, '%3A')
.replace(/,/g, '%2C');
}
//# sourceMappingURL=command.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,uCAAwB;AACxB,mCAAsC;AAWtC;;;;;;;;;GASG;AACH,SAAgB,YAAY,CAC1B,OAAe,EACf,UAA6B,EAC7B,OAAY;IAEZ,MAAM,GAAG,GAAG,IAAI,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,CAAA;IACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAA;AAC/C,CAAC;AAPD,oCAOC;AAED,SAAgB,KAAK,CAAC,IAAY,EAAE,OAAO,GAAG,EAAE;IAC9C,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,OAAO,CAAC,CAAA;AACjC,CAAC;AAFD,sBAEC;AAED,MAAM,UAAU,GAAG,IAAI,CAAA;AAEvB,MAAM,OAAO;IAKX,YAAY,OAAe,EAAE,UAA6B,EAAE,OAAe;QACzE,IAAI,CAAC,OAAO,EAAE;YACZ,OAAO,GAAG,iBAAiB,CAAA;SAC5B;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;IACxB,CAAC;IAED,QAAQ;QACN,IAAI,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC,OAAO,CAAA;QAEtC,IAAI,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE;YAC9D,MAAM,IAAI,GAAG,CAAA;YACb,IAAI,KAAK,GAAG,IAAI,CAAA;YAChB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE;gBACjC,IAAI,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE;oBACvC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;oBAChC,IAAI,GAAG,EAAE;wBACP,IAAI,KAAK,EAAE;4BACT,KAAK,GAAG,KAAK,CAAA;yBACd;6BAAM;4BACL,MAAM,IAAI,GAAG,CAAA;yBACd;wBAED,MAAM,IAAI,GAAG,GAAG,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAA;qBAC1C;iBACF;aACF;SACF;QAED,MAAM,IAAI,GAAG,UAAU,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAA;QACpD,OAAO,MAAM,CAAA;IACf,CAAC;CACF;AAED,SAAS,UAAU,CAAC,CAAM;IACxB,OAAO,sBAAc,CAAC,CAAC,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AAC1B,CAAC;AAED,SAAS,cAAc,CAAC,CAAM;IAC5B,OAAO,sBAAc,CAAC,CAAC,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC;SACpB,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AACzB,CAAC"}
-198
View File
@@ -1,198 +0,0 @@
/**
* Interface for getInput options
*/
export interface InputOptions {
/** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */
required?: boolean;
/** Optional. Whether leading/trailing whitespace will be trimmed for the input. Defaults to true */
trimWhitespace?: boolean;
}
/**
* The code to exit an action
*/
export declare enum ExitCode {
/**
* A code indicating that the action was successful
*/
Success = 0,
/**
* A code indicating that the action was a failure
*/
Failure = 1
}
/**
* Optional properties that can be sent with annotation commands (notice, error, and warning)
* See: https://docs.github.com/en/rest/reference/checks#create-a-check-run for more information about annotations.
*/
export interface AnnotationProperties {
/**
* A title for the annotation.
*/
title?: string;
/**
* The path of the file for which the annotation should be created.
*/
file?: string;
/**
* The start line for the annotation.
*/
startLine?: number;
/**
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
*/
endLine?: number;
/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
*/
startColumn?: number;
/**
* The end column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
* Defaults to `startColumn` when `startColumn` is provided.
*/
endColumn?: number;
}
/**
* Sets env variable for this action and future actions in the job
* @param name the name of the variable to set
* @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify
*/
export declare function exportVariable(name: string, val: any): void;
/**
* Registers a secret which will get masked from logs
* @param secret value of the secret
*/
export declare function setSecret(secret: string): void;
/**
* Prepends inputPath to the PATH (for this action and future actions)
* @param inputPath
*/
export declare function addPath(inputPath: string): void;
/**
* Gets the value of an input.
* Unless trimWhitespace is set to false in InputOptions, the value is also trimmed.
* Returns an empty string if the value is not defined.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string
*/
export declare function getInput(name: string, options?: InputOptions): string;
/**
* Gets the values of an multiline input. Each value is also trimmed.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string[]
*
*/
export declare function getMultilineInput(name: string, options?: InputOptions): string[];
/**
* Gets the input value of the boolean type in the YAML 1.2 "core schema" specification.
* Support boolean input list: `true | True | TRUE | false | False | FALSE` .
* The return value is also in boolean type.
* ref: https://yaml.org/spec/1.2/spec.html#id2804923
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns boolean
*/
export declare function getBooleanInput(name: string, options?: InputOptions): boolean;
/**
* Sets the value of an output.
*
* @param name name of the output to set
* @param value value to store. Non-string values will be converted to a string via JSON.stringify
*/
export declare function setOutput(name: string, value: any): void;
/**
* Enables or disables the echoing of commands into stdout for the rest of the step.
* Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set.
*
*/
export declare function setCommandEcho(enabled: boolean): void;
/**
* Sets the action status to failed.
* When the action exits it will be with an exit code of 1
* @param message add error issue message
*/
export declare function setFailed(message: string | Error): void;
/**
* Gets whether Actions Step Debug is on or not
*/
export declare function isDebug(): boolean;
/**
* Writes debug message to user log
* @param message debug message
*/
export declare function debug(message: string): void;
/**
* Adds an error issue
* @param message error issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export declare function error(message: string | Error, properties?: AnnotationProperties): void;
/**
* Adds a warning issue
* @param message warning issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export declare function warning(message: string | Error, properties?: AnnotationProperties): void;
/**
* Adds a notice issue
* @param message notice issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export declare function notice(message: string | Error, properties?: AnnotationProperties): void;
/**
* Writes info to log with console.log.
* @param message info message
*/
export declare function info(message: string): void;
/**
* Begin an output group.
*
* Output until the next `groupEnd` will be foldable in this group
*
* @param name The name of the output group
*/
export declare function startGroup(name: string): void;
/**
* End an output group.
*/
export declare function endGroup(): void;
/**
* Wrap an asynchronous function call in a group.
*
* Returns the same type as the function itself.
*
* @param name The name of the group
* @param fn The function to wrap in the group
*/
export declare function group<T>(name: string, fn: () => Promise<T>): Promise<T>;
/**
* Saves state for current action, the state can only be retrieved by this action's post job execution.
*
* @param name name of the state to store
* @param value value to store. Non-string values will be converted to a string via JSON.stringify
*/
export declare function saveState(name: string, value: any): void;
/**
* Gets the value of an state set by this action's main execution.
*
* @param name name of the state to get
* @returns string
*/
export declare function getState(name: string): string;
export declare function getIDToken(aud?: string): Promise<string>;
/**
* Summary exports
*/
export { summary } from './summary';
/**
* @deprecated use core.summary
*/
export { markdownSummary } from './summary';
/**
* Path exports
*/
export { toPosixPath, toWin32Path, toPlatformPath } from './path-utils';
-336
View File
@@ -1,336 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIDToken = exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.notice = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0;
const command_1 = require("./command");
const file_command_1 = require("./file-command");
const utils_1 = require("./utils");
const os = __importStar(require("os"));
const path = __importStar(require("path"));
const oidc_utils_1 = require("./oidc-utils");
/**
* The code to exit an action
*/
var ExitCode;
(function (ExitCode) {
/**
* A code indicating that the action was successful
*/
ExitCode[ExitCode["Success"] = 0] = "Success";
/**
* A code indicating that the action was a failure
*/
ExitCode[ExitCode["Failure"] = 1] = "Failure";
})(ExitCode = exports.ExitCode || (exports.ExitCode = {}));
//-----------------------------------------------------------------------
// Variables
//-----------------------------------------------------------------------
/**
* Sets env variable for this action and future actions in the job
* @param name the name of the variable to set
* @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function exportVariable(name, val) {
const convertedVal = utils_1.toCommandValue(val);
process.env[name] = convertedVal;
const filePath = process.env['GITHUB_ENV'] || '';
if (filePath) {
return file_command_1.issueFileCommand('ENV', file_command_1.prepareKeyValueMessage(name, val));
}
command_1.issueCommand('set-env', { name }, convertedVal);
}
exports.exportVariable = exportVariable;
/**
* Registers a secret which will get masked from logs
* @param secret value of the secret
*/
function setSecret(secret) {
command_1.issueCommand('add-mask', {}, secret);
}
exports.setSecret = setSecret;
/**
* Prepends inputPath to the PATH (for this action and future actions)
* @param inputPath
*/
function addPath(inputPath) {
const filePath = process.env['GITHUB_PATH'] || '';
if (filePath) {
file_command_1.issueFileCommand('PATH', inputPath);
}
else {
command_1.issueCommand('add-path', {}, inputPath);
}
process.env['PATH'] = `${inputPath}${path.delimiter}${process.env['PATH']}`;
}
exports.addPath = addPath;
/**
* Gets the value of an input.
* Unless trimWhitespace is set to false in InputOptions, the value is also trimmed.
* Returns an empty string if the value is not defined.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string
*/
function getInput(name, options) {
const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
if (options && options.required && !val) {
throw new Error(`Input required and not supplied: ${name}`);
}
if (options && options.trimWhitespace === false) {
return val;
}
return val.trim();
}
exports.getInput = getInput;
/**
* Gets the values of an multiline input. Each value is also trimmed.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string[]
*
*/
function getMultilineInput(name, options) {
const inputs = getInput(name, options)
.split('\n')
.filter(x => x !== '');
if (options && options.trimWhitespace === false) {
return inputs;
}
return inputs.map(input => input.trim());
}
exports.getMultilineInput = getMultilineInput;
/**
* Gets the input value of the boolean type in the YAML 1.2 "core schema" specification.
* Support boolean input list: `true | True | TRUE | false | False | FALSE` .
* The return value is also in boolean type.
* ref: https://yaml.org/spec/1.2/spec.html#id2804923
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns boolean
*/
function getBooleanInput(name, options) {
const trueValue = ['true', 'True', 'TRUE'];
const falseValue = ['false', 'False', 'FALSE'];
const val = getInput(name, options);
if (trueValue.includes(val))
return true;
if (falseValue.includes(val))
return false;
throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` +
`Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
}
exports.getBooleanInput = getBooleanInput;
/**
* Sets the value of an output.
*
* @param name name of the output to set
* @param value value to store. Non-string values will be converted to a string via JSON.stringify
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) {
const filePath = process.env['GITHUB_OUTPUT'] || '';
if (filePath) {
return file_command_1.issueFileCommand('OUTPUT', file_command_1.prepareKeyValueMessage(name, value));
}
process.stdout.write(os.EOL);
command_1.issueCommand('set-output', { name }, utils_1.toCommandValue(value));
}
exports.setOutput = setOutput;
/**
* Enables or disables the echoing of commands into stdout for the rest of the step.
* Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set.
*
*/
function setCommandEcho(enabled) {
command_1.issue('echo', enabled ? 'on' : 'off');
}
exports.setCommandEcho = setCommandEcho;
//-----------------------------------------------------------------------
// Results
//-----------------------------------------------------------------------
/**
* Sets the action status to failed.
* When the action exits it will be with an exit code of 1
* @param message add error issue message
*/
function setFailed(message) {
process.exitCode = ExitCode.Failure;
error(message);
}
exports.setFailed = setFailed;
//-----------------------------------------------------------------------
// Logging Commands
//-----------------------------------------------------------------------
/**
* Gets whether Actions Step Debug is on or not
*/
function isDebug() {
return process.env['RUNNER_DEBUG'] === '1';
}
exports.isDebug = isDebug;
/**
* Writes debug message to user log
* @param message debug message
*/
function debug(message) {
command_1.issueCommand('debug', {}, message);
}
exports.debug = debug;
/**
* Adds an error issue
* @param message error issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
function error(message, properties = {}) {
command_1.issueCommand('error', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
exports.error = error;
/**
* Adds a warning issue
* @param message warning issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
function warning(message, properties = {}) {
command_1.issueCommand('warning', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
exports.warning = warning;
/**
* Adds a notice issue
* @param message notice issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
function notice(message, properties = {}) {
command_1.issueCommand('notice', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
exports.notice = notice;
/**
* Writes info to log with console.log.
* @param message info message
*/
function info(message) {
process.stdout.write(message + os.EOL);
}
exports.info = info;
/**
* Begin an output group.
*
* Output until the next `groupEnd` will be foldable in this group
*
* @param name The name of the output group
*/
function startGroup(name) {
command_1.issue('group', name);
}
exports.startGroup = startGroup;
/**
* End an output group.
*/
function endGroup() {
command_1.issue('endgroup');
}
exports.endGroup = endGroup;
/**
* Wrap an asynchronous function call in a group.
*
* Returns the same type as the function itself.
*
* @param name The name of the group
* @param fn The function to wrap in the group
*/
function group(name, fn) {
return __awaiter(this, void 0, void 0, function* () {
startGroup(name);
let result;
try {
result = yield fn();
}
finally {
endGroup();
}
return result;
});
}
exports.group = group;
//-----------------------------------------------------------------------
// Wrapper action state
//-----------------------------------------------------------------------
/**
* Saves state for current action, the state can only be retrieved by this action's post job execution.
*
* @param name name of the state to store
* @param value value to store. Non-string values will be converted to a string via JSON.stringify
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function saveState(name, value) {
const filePath = process.env['GITHUB_STATE'] || '';
if (filePath) {
return file_command_1.issueFileCommand('STATE', file_command_1.prepareKeyValueMessage(name, value));
}
command_1.issueCommand('save-state', { name }, utils_1.toCommandValue(value));
}
exports.saveState = saveState;
/**
* Gets the value of an state set by this action's main execution.
*
* @param name name of the state to get
* @returns string
*/
function getState(name) {
return process.env[`STATE_${name}`] || '';
}
exports.getState = getState;
function getIDToken(aud) {
return __awaiter(this, void 0, void 0, function* () {
return yield oidc_utils_1.OidcClient.getIDToken(aud);
});
}
exports.getIDToken = getIDToken;
/**
* Summary exports
*/
var summary_1 = require("./summary");
Object.defineProperty(exports, "summary", { enumerable: true, get: function () { return summary_1.summary; } });
/**
* @deprecated use core.summary
*/
var summary_2 = require("./summary");
Object.defineProperty(exports, "markdownSummary", { enumerable: true, get: function () { return summary_2.markdownSummary; } });
/**
* Path exports
*/
var path_utils_1 = require("./path-utils");
Object.defineProperty(exports, "toPosixPath", { enumerable: true, get: function () { return path_utils_1.toPosixPath; } });
Object.defineProperty(exports, "toWin32Path", { enumerable: true, get: function () { return path_utils_1.toWin32Path; } });
Object.defineProperty(exports, "toPlatformPath", { enumerable: true, get: function () { return path_utils_1.toPlatformPath; } });
//# sourceMappingURL=core.js.map
-1
View File
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
export declare function issueFileCommand(command: string, message: any): void;
export declare function prepareKeyValueMessage(key: string, value: any): string;
-58
View File
@@ -1,58 +0,0 @@
"use strict";
// For internal use, subject to change.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.prepareKeyValueMessage = exports.issueFileCommand = void 0;
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const uuid_1 = require("uuid");
const utils_1 = require("./utils");
function issueFileCommand(command, message) {
const filePath = process.env[`GITHUB_${command}`];
if (!filePath) {
throw new Error(`Unable to find environment variable for file command ${command}`);
}
if (!fs.existsSync(filePath)) {
throw new Error(`Missing file at path: ${filePath}`);
}
fs.appendFileSync(filePath, `${utils_1.toCommandValue(message)}${os.EOL}`, {
encoding: 'utf8'
});
}
exports.issueFileCommand = issueFileCommand;
function prepareKeyValueMessage(key, value) {
const delimiter = `ghadelimiter_${uuid_1.v4()}`;
const convertedValue = utils_1.toCommandValue(value);
// These should realistically never happen, but just in case someone finds a
// way to exploit uuid generation let's not allow keys or values that contain
// the delimiter.
if (key.includes(delimiter)) {
throw new Error(`Unexpected input: name should not contain the delimiter "${delimiter}"`);
}
if (convertedValue.includes(delimiter)) {
throw new Error(`Unexpected input: value should not contain the delimiter "${delimiter}"`);
}
return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`;
}
exports.prepareKeyValueMessage = prepareKeyValueMessage;
//# sourceMappingURL=file-command.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"file-command.js","sourceRoot":"","sources":["../src/file-command.ts"],"names":[],"mappings":";AAAA,uCAAuC;;;;;;;;;;;;;;;;;;;;;;AAEvC,mCAAmC;AACnC,uDAAuD;AAEvD,uCAAwB;AACxB,uCAAwB;AACxB,+BAAiC;AACjC,mCAAsC;AAEtC,SAAgB,gBAAgB,CAAC,OAAe,EAAE,OAAY;IAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAA;IACjD,IAAI,CAAC,QAAQ,EAAE;QACb,MAAM,IAAI,KAAK,CACb,wDAAwD,OAAO,EAAE,CAClE,CAAA;KACF;IACD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QAC5B,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,EAAE,CAAC,CAAA;KACrD;IAED,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE,GAAG,sBAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE;QACjE,QAAQ,EAAE,MAAM;KACjB,CAAC,CAAA;AACJ,CAAC;AAdD,4CAcC;AAED,SAAgB,sBAAsB,CAAC,GAAW,EAAE,KAAU;IAC5D,MAAM,SAAS,GAAG,gBAAgB,SAAM,EAAE,EAAE,CAAA;IAC5C,MAAM,cAAc,GAAG,sBAAc,CAAC,KAAK,CAAC,CAAA;IAE5C,4EAA4E;IAC5E,6EAA6E;IAC7E,iBAAiB;IACjB,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QAC3B,MAAM,IAAI,KAAK,CACb,4DAA4D,SAAS,GAAG,CACzE,CAAA;KACF;IAED,IAAI,cAAc,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;QACtC,MAAM,IAAI,KAAK,CACb,6DAA6D,SAAS,GAAG,CAC1E,CAAA;KACF;IAED,OAAO,GAAG,GAAG,KAAK,SAAS,GAAG,EAAE,CAAC,GAAG,GAAG,cAAc,GAAG,EAAE,CAAC,GAAG,GAAG,SAAS,EAAE,CAAA;AAC9E,CAAC;AApBD,wDAoBC"}
-7
View File
@@ -1,7 +0,0 @@
export declare class OidcClient {
private static createHttpClient;
private static getRequestToken;
private static getIDTokenUrl;
private static getCall;
static getIDToken(audience?: string): Promise<string>;
}
-77
View File
@@ -1,77 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OidcClient = void 0;
const http_client_1 = require("@actions/http-client");
const auth_1 = require("@actions/http-client/lib/auth");
const core_1 = require("./core");
class OidcClient {
static createHttpClient(allowRetry = true, maxRetry = 10) {
const requestOptions = {
allowRetries: allowRetry,
maxRetries: maxRetry
};
return new http_client_1.HttpClient('actions/oidc-client', [new auth_1.BearerCredentialHandler(OidcClient.getRequestToken())], requestOptions);
}
static getRequestToken() {
const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'];
if (!token) {
throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable');
}
return token;
}
static getIDTokenUrl() {
const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL'];
if (!runtimeUrl) {
throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable');
}
return runtimeUrl;
}
static getCall(id_token_url) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const httpclient = OidcClient.createHttpClient();
const res = yield httpclient
.getJson(id_token_url)
.catch(error => {
throw new Error(`Failed to get ID Token. \n
Error Code : ${error.statusCode}\n
Error Message: ${error.message}`);
});
const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value;
if (!id_token) {
throw new Error('Response json body do not have ID Token field');
}
return id_token;
});
}
static getIDToken(audience) {
return __awaiter(this, void 0, void 0, function* () {
try {
// New ID Token is requested from action service
let id_token_url = OidcClient.getIDTokenUrl();
if (audience) {
const encodedAudience = encodeURIComponent(audience);
id_token_url = `${id_token_url}&audience=${encodedAudience}`;
}
core_1.debug(`ID token url is ${id_token_url}`);
const id_token = yield OidcClient.getCall(id_token_url);
core_1.setSecret(id_token);
return id_token;
}
catch (error) {
throw new Error(`Error message: ${error.message}`);
}
});
}
}
exports.OidcClient = OidcClient;
//# sourceMappingURL=oidc-utils.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"oidc-utils.js","sourceRoot":"","sources":["../src/oidc-utils.ts"],"names":[],"mappings":";;;;;;;;;;;;AAGA,sDAA+C;AAC/C,wDAAqE;AACrE,iCAAuC;AAKvC,MAAa,UAAU;IACb,MAAM,CAAC,gBAAgB,CAC7B,UAAU,GAAG,IAAI,EACjB,QAAQ,GAAG,EAAE;QAEb,MAAM,cAAc,GAAmB;YACrC,YAAY,EAAE,UAAU;YACxB,UAAU,EAAE,QAAQ;SACrB,CAAA;QAED,OAAO,IAAI,wBAAU,CACnB,qBAAqB,EACrB,CAAC,IAAI,8BAAuB,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC,EAC3D,cAAc,CACf,CAAA;IACH,CAAC;IAEO,MAAM,CAAC,eAAe;QAC5B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAA;QAC3D,IAAI,CAAC,KAAK,EAAE;YACV,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAA;SACF;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IAEO,MAAM,CAAC,aAAa;QAC1B,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAA;QAC9D,IAAI,CAAC,UAAU,EAAE;YACf,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;SAC3E;QACD,OAAO,UAAU,CAAA;IACnB,CAAC;IAEO,MAAM,CAAO,OAAO,CAAC,YAAoB;;;YAC/C,MAAM,UAAU,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAA;YAEhD,MAAM,GAAG,GAAG,MAAM,UAAU;iBACzB,OAAO,CAAgB,YAAY,CAAC;iBACpC,KAAK,CAAC,KAAK,CAAC,EAAE;gBACb,MAAM,IAAI,KAAK,CACb;uBACa,KAAK,CAAC,UAAU;yBACd,KAAK,CAAC,OAAO,EAAE,CAC/B,CAAA;YACH,CAAC,CAAC,CAAA;YAEJ,MAAM,QAAQ,SAAG,GAAG,CAAC,MAAM,0CAAE,KAAK,CAAA;YAClC,IAAI,CAAC,QAAQ,EAAE;gBACb,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;aACjE;YACD,OAAO,QAAQ,CAAA;;KAChB;IAED,MAAM,CAAO,UAAU,CAAC,QAAiB;;YACvC,IAAI;gBACF,gDAAgD;gBAChD,IAAI,YAAY,GAAW,UAAU,CAAC,aAAa,EAAE,CAAA;gBACrD,IAAI,QAAQ,EAAE;oBACZ,MAAM,eAAe,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAA;oBACpD,YAAY,GAAG,GAAG,YAAY,aAAa,eAAe,EAAE,CAAA;iBAC7D;gBAED,YAAK,CAAC,mBAAmB,YAAY,EAAE,CAAC,CAAA;gBAExC,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;gBACvD,gBAAS,CAAC,QAAQ,CAAC,CAAA;gBACnB,OAAO,QAAQ,CAAA;aAChB;YAAC,OAAO,KAAK,EAAE;gBACd,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAA;aACnD;QACH,CAAC;KAAA;CACF;AAzED,gCAyEC"}
-25
View File
@@ -1,25 +0,0 @@
/**
* toPosixPath converts the given path to the posix form. On Windows, \\ will be
* replaced with /.
*
* @param pth. Path to transform.
* @return string Posix path.
*/
export declare function toPosixPath(pth: string): string;
/**
* toWin32Path converts the given path to the win32 form. On Linux, / will be
* replaced with \\.
*
* @param pth. Path to transform.
* @return string Win32 path.
*/
export declare function toWin32Path(pth: string): string;
/**
* toPlatformPath converts the given path to a platform-specific path. It does
* this by replacing instances of / and \ with the platform-specific path
* separator.
*
* @param pth The path to platformize.
* @return string The platform-specific path.
*/
export declare function toPlatformPath(pth: string): string;
-58
View File
@@ -1,58 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.toPlatformPath = exports.toWin32Path = exports.toPosixPath = void 0;
const path = __importStar(require("path"));
/**
* toPosixPath converts the given path to the posix form. On Windows, \\ will be
* replaced with /.
*
* @param pth. Path to transform.
* @return string Posix path.
*/
function toPosixPath(pth) {
return pth.replace(/[\\]/g, '/');
}
exports.toPosixPath = toPosixPath;
/**
* toWin32Path converts the given path to the win32 form. On Linux, / will be
* replaced with \\.
*
* @param pth. Path to transform.
* @return string Win32 path.
*/
function toWin32Path(pth) {
return pth.replace(/[/]/g, '\\');
}
exports.toWin32Path = toWin32Path;
/**
* toPlatformPath converts the given path to a platform-specific path. It does
* this by replacing instances of / and \ with the platform-specific path
* separator.
*
* @param pth The path to platformize.
* @return string The platform-specific path.
*/
function toPlatformPath(pth) {
return pth.replace(/[/\\]/g, path.sep);
}
exports.toPlatformPath = toPlatformPath;
//# sourceMappingURL=path-utils.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../src/path-utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA4B;AAE5B;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,GAAW;IACrC,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;AAClC,CAAC;AAFD,kCAEC;AAED;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,GAAW;IACrC,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;AAClC,CAAC;AAFD,kCAEC;AAED;;;;;;;GAOG;AACH,SAAgB,cAAc,CAAC,GAAW;IACxC,OAAO,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAA;AACxC,CAAC;AAFD,wCAEC"}
-202
View File
@@ -1,202 +0,0 @@
export declare const SUMMARY_ENV_VAR = "GITHUB_STEP_SUMMARY";
export declare const SUMMARY_DOCS_URL = "https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary";
export declare type SummaryTableRow = (SummaryTableCell | string)[];
export interface SummaryTableCell {
/**
* Cell content
*/
data: string;
/**
* Render cell as header
* (optional) default: false
*/
header?: boolean;
/**
* Number of columns the cell extends
* (optional) default: '1'
*/
colspan?: string;
/**
* Number of rows the cell extends
* (optional) default: '1'
*/
rowspan?: string;
}
export interface SummaryImageOptions {
/**
* The width of the image in pixels. Must be an integer without a unit.
* (optional)
*/
width?: string;
/**
* The height of the image in pixels. Must be an integer without a unit.
* (optional)
*/
height?: string;
}
export interface SummaryWriteOptions {
/**
* Replace all existing content in summary file with buffer contents
* (optional) default: false
*/
overwrite?: boolean;
}
declare class Summary {
private _buffer;
private _filePath?;
constructor();
/**
* Finds the summary file path from the environment, rejects if env var is not found or file does not exist
* Also checks r/w permissions.
*
* @returns step summary file path
*/
private filePath;
/**
* Wraps content in an HTML tag, adding any HTML attributes
*
* @param {string} tag HTML tag to wrap
* @param {string | null} content content within the tag
* @param {[attribute: string]: string} attrs key-value list of HTML attributes to add
*
* @returns {string} content wrapped in HTML element
*/
private wrap;
/**
* Writes text in the buffer to the summary buffer file and empties buffer. Will append by default.
*
* @param {SummaryWriteOptions} [options] (optional) options for write operation
*
* @returns {Promise<Summary>} summary instance
*/
write(options?: SummaryWriteOptions): Promise<Summary>;
/**
* Clears the summary buffer and wipes the summary file
*
* @returns {Summary} summary instance
*/
clear(): Promise<Summary>;
/**
* Returns the current summary buffer as a string
*
* @returns {string} string of summary buffer
*/
stringify(): string;
/**
* If the summary buffer is empty
*
* @returns {boolen} true if the buffer is empty
*/
isEmptyBuffer(): boolean;
/**
* Resets the summary buffer without writing to summary file
*
* @returns {Summary} summary instance
*/
emptyBuffer(): Summary;
/**
* Adds raw text to the summary buffer
*
* @param {string} text content to add
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
*
* @returns {Summary} summary instance
*/
addRaw(text: string, addEOL?: boolean): Summary;
/**
* Adds the operating system-specific end-of-line marker to the buffer
*
* @returns {Summary} summary instance
*/
addEOL(): Summary;
/**
* Adds an HTML codeblock to the summary buffer
*
* @param {string} code content to render within fenced code block
* @param {string} lang (optional) language to syntax highlight code
*
* @returns {Summary} summary instance
*/
addCodeBlock(code: string, lang?: string): Summary;
/**
* Adds an HTML list to the summary buffer
*
* @param {string[]} items list of items to render
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
*
* @returns {Summary} summary instance
*/
addList(items: string[], ordered?: boolean): Summary;
/**
* Adds an HTML table to the summary buffer
*
* @param {SummaryTableCell[]} rows table rows
*
* @returns {Summary} summary instance
*/
addTable(rows: SummaryTableRow[]): Summary;
/**
* Adds a collapsable HTML details element to the summary buffer
*
* @param {string} label text for the closed state
* @param {string} content collapsable content
*
* @returns {Summary} summary instance
*/
addDetails(label: string, content: string): Summary;
/**
* Adds an HTML image tag to the summary buffer
*
* @param {string} src path to the image you to embed
* @param {string} alt text description of the image
* @param {SummaryImageOptions} options (optional) addition image attributes
*
* @returns {Summary} summary instance
*/
addImage(src: string, alt: string, options?: SummaryImageOptions): Summary;
/**
* Adds an HTML section heading element
*
* @param {string} text heading text
* @param {number | string} [level=1] (optional) the heading level, default: 1
*
* @returns {Summary} summary instance
*/
addHeading(text: string, level?: number | string): Summary;
/**
* Adds an HTML thematic break (<hr>) to the summary buffer
*
* @returns {Summary} summary instance
*/
addSeparator(): Summary;
/**
* Adds an HTML line break (<br>) to the summary buffer
*
* @returns {Summary} summary instance
*/
addBreak(): Summary;
/**
* Adds an HTML blockquote to the summary buffer
*
* @param {string} text quote text
* @param {string} cite (optional) citation url
*
* @returns {Summary} summary instance
*/
addQuote(text: string, cite?: string): Summary;
/**
* Adds an HTML anchor tag to the summary buffer
*
* @param {string} text link text/content
* @param {string} href hyperlink
*
* @returns {Summary} summary instance
*/
addLink(text: string, href: string): Summary;
}
/**
* @deprecated use `core.summary`
*/
export declare const markdownSummary: Summary;
export declare const summary: Summary;
export {};
-283
View File
@@ -1,283 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.summary = exports.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0;
const os_1 = require("os");
const fs_1 = require("fs");
const { access, appendFile, writeFile } = fs_1.promises;
exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY';
exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary';
class Summary {
constructor() {
this._buffer = '';
}
/**
* Finds the summary file path from the environment, rejects if env var is not found or file does not exist
* Also checks r/w permissions.
*
* @returns step summary file path
*/
filePath() {
return __awaiter(this, void 0, void 0, function* () {
if (this._filePath) {
return this._filePath;
}
const pathFromEnv = process.env[exports.SUMMARY_ENV_VAR];
if (!pathFromEnv) {
throw new Error(`Unable to find environment variable for $${exports.SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`);
}
try {
yield access(pathFromEnv, fs_1.constants.R_OK | fs_1.constants.W_OK);
}
catch (_a) {
throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`);
}
this._filePath = pathFromEnv;
return this._filePath;
});
}
/**
* Wraps content in an HTML tag, adding any HTML attributes
*
* @param {string} tag HTML tag to wrap
* @param {string | null} content content within the tag
* @param {[attribute: string]: string} attrs key-value list of HTML attributes to add
*
* @returns {string} content wrapped in HTML element
*/
wrap(tag, content, attrs = {}) {
const htmlAttrs = Object.entries(attrs)
.map(([key, value]) => ` ${key}="${value}"`)
.join('');
if (!content) {
return `<${tag}${htmlAttrs}>`;
}
return `<${tag}${htmlAttrs}>${content}</${tag}>`;
}
/**
* Writes text in the buffer to the summary buffer file and empties buffer. Will append by default.
*
* @param {SummaryWriteOptions} [options] (optional) options for write operation
*
* @returns {Promise<Summary>} summary instance
*/
write(options) {
return __awaiter(this, void 0, void 0, function* () {
const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite);
const filePath = yield this.filePath();
const writeFunc = overwrite ? writeFile : appendFile;
yield writeFunc(filePath, this._buffer, { encoding: 'utf8' });
return this.emptyBuffer();
});
}
/**
* Clears the summary buffer and wipes the summary file
*
* @returns {Summary} summary instance
*/
clear() {
return __awaiter(this, void 0, void 0, function* () {
return this.emptyBuffer().write({ overwrite: true });
});
}
/**
* Returns the current summary buffer as a string
*
* @returns {string} string of summary buffer
*/
stringify() {
return this._buffer;
}
/**
* If the summary buffer is empty
*
* @returns {boolen} true if the buffer is empty
*/
isEmptyBuffer() {
return this._buffer.length === 0;
}
/**
* Resets the summary buffer without writing to summary file
*
* @returns {Summary} summary instance
*/
emptyBuffer() {
this._buffer = '';
return this;
}
/**
* Adds raw text to the summary buffer
*
* @param {string} text content to add
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
*
* @returns {Summary} summary instance
*/
addRaw(text, addEOL = false) {
this._buffer += text;
return addEOL ? this.addEOL() : this;
}
/**
* Adds the operating system-specific end-of-line marker to the buffer
*
* @returns {Summary} summary instance
*/
addEOL() {
return this.addRaw(os_1.EOL);
}
/**
* Adds an HTML codeblock to the summary buffer
*
* @param {string} code content to render within fenced code block
* @param {string} lang (optional) language to syntax highlight code
*
* @returns {Summary} summary instance
*/
addCodeBlock(code, lang) {
const attrs = Object.assign({}, (lang && { lang }));
const element = this.wrap('pre', this.wrap('code', code), attrs);
return this.addRaw(element).addEOL();
}
/**
* Adds an HTML list to the summary buffer
*
* @param {string[]} items list of items to render
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
*
* @returns {Summary} summary instance
*/
addList(items, ordered = false) {
const tag = ordered ? 'ol' : 'ul';
const listItems = items.map(item => this.wrap('li', item)).join('');
const element = this.wrap(tag, listItems);
return this.addRaw(element).addEOL();
}
/**
* Adds an HTML table to the summary buffer
*
* @param {SummaryTableCell[]} rows table rows
*
* @returns {Summary} summary instance
*/
addTable(rows) {
const tableBody = rows
.map(row => {
const cells = row
.map(cell => {
if (typeof cell === 'string') {
return this.wrap('td', cell);
}
const { header, data, colspan, rowspan } = cell;
const tag = header ? 'th' : 'td';
const attrs = Object.assign(Object.assign({}, (colspan && { colspan })), (rowspan && { rowspan }));
return this.wrap(tag, data, attrs);
})
.join('');
return this.wrap('tr', cells);
})
.join('');
const element = this.wrap('table', tableBody);
return this.addRaw(element).addEOL();
}
/**
* Adds a collapsable HTML details element to the summary buffer
*
* @param {string} label text for the closed state
* @param {string} content collapsable content
*
* @returns {Summary} summary instance
*/
addDetails(label, content) {
const element = this.wrap('details', this.wrap('summary', label) + content);
return this.addRaw(element).addEOL();
}
/**
* Adds an HTML image tag to the summary buffer
*
* @param {string} src path to the image you to embed
* @param {string} alt text description of the image
* @param {SummaryImageOptions} options (optional) addition image attributes
*
* @returns {Summary} summary instance
*/
addImage(src, alt, options) {
const { width, height } = options || {};
const attrs = Object.assign(Object.assign({}, (width && { width })), (height && { height }));
const element = this.wrap('img', null, Object.assign({ src, alt }, attrs));
return this.addRaw(element).addEOL();
}
/**
* Adds an HTML section heading element
*
* @param {string} text heading text
* @param {number | string} [level=1] (optional) the heading level, default: 1
*
* @returns {Summary} summary instance
*/
addHeading(text, level) {
const tag = `h${level}`;
const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
? tag
: 'h1';
const element = this.wrap(allowedTag, text);
return this.addRaw(element).addEOL();
}
/**
* Adds an HTML thematic break (<hr>) to the summary buffer
*
* @returns {Summary} summary instance
*/
addSeparator() {
const element = this.wrap('hr', null);
return this.addRaw(element).addEOL();
}
/**
* Adds an HTML line break (<br>) to the summary buffer
*
* @returns {Summary} summary instance
*/
addBreak() {
const element = this.wrap('br', null);
return this.addRaw(element).addEOL();
}
/**
* Adds an HTML blockquote to the summary buffer
*
* @param {string} text quote text
* @param {string} cite (optional) citation url
*
* @returns {Summary} summary instance
*/
addQuote(text, cite) {
const attrs = Object.assign({}, (cite && { cite }));
const element = this.wrap('blockquote', text, attrs);
return this.addRaw(element).addEOL();
}
/**
* Adds an HTML anchor tag to the summary buffer
*
* @param {string} text link text/content
* @param {string} href hyperlink
*
* @returns {Summary} summary instance
*/
addLink(text, href) {
const element = this.wrap('a', text, { href });
return this.addRaw(element).addEOL();
}
}
const _summary = new Summary();
/**
* @deprecated use `core.summary`
*/
exports.markdownSummary = _summary;
exports.summary = _summary;
//# sourceMappingURL=summary.js.map
File diff suppressed because one or more lines are too long
-14
View File
@@ -1,14 +0,0 @@
import { AnnotationProperties } from './core';
import { CommandProperties } from './command';
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
*/
export declare function toCommandValue(input: any): string;
/**
*
* @param annotationProperties
* @returns The command properties to send with the actual annotation command
* See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
*/
export declare function toCommandProperties(annotationProperties: AnnotationProperties): CommandProperties;
-40
View File
@@ -1,40 +0,0 @@
"use strict";
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true });
exports.toCommandProperties = exports.toCommandValue = void 0;
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
*/
function toCommandValue(input) {
if (input === null || input === undefined) {
return '';
}
else if (typeof input === 'string' || input instanceof String) {
return input;
}
return JSON.stringify(input);
}
exports.toCommandValue = toCommandValue;
/**
*
* @param annotationProperties
* @returns The command properties to send with the actual annotation command
* See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
*/
function toCommandProperties(annotationProperties) {
if (!Object.keys(annotationProperties).length) {
return {};
}
return {
title: annotationProperties.title,
file: annotationProperties.file,
line: annotationProperties.startLine,
endLine: annotationProperties.endLine,
col: annotationProperties.startColumn,
endColumn: annotationProperties.endColumn
};
}
exports.toCommandProperties = toCommandProperties;
//# sourceMappingURL=utils.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";AAAA,mCAAmC;AACnC,uDAAuD;;;AAKvD;;;GAGG;AACH,SAAgB,cAAc,CAAC,KAAU;IACvC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE;QACzC,OAAO,EAAE,CAAA;KACV;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,YAAY,MAAM,EAAE;QAC/D,OAAO,KAAe,CAAA;KACvB;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;AAC9B,CAAC;AAPD,wCAOC;AAED;;;;;GAKG;AACH,SAAgB,mBAAmB,CACjC,oBAA0C;IAE1C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,MAAM,EAAE;QAC7C,OAAO,EAAE,CAAA;KACV;IAED,OAAO;QACL,KAAK,EAAE,oBAAoB,CAAC,KAAK;QACjC,IAAI,EAAE,oBAAoB,CAAC,IAAI;QAC/B,IAAI,EAAE,oBAAoB,CAAC,SAAS;QACpC,OAAO,EAAE,oBAAoB,CAAC,OAAO;QACrC,GAAG,EAAE,oBAAoB,CAAC,WAAW;QACrC,SAAS,EAAE,oBAAoB,CAAC,SAAS;KAC1C,CAAA;AACH,CAAC;AAfD,kDAeC"}
-46
View File
@@ -1,46 +0,0 @@
{
"name": "@actions/core",
"version": "1.10.1",
"description": "Actions core lib",
"keywords": [
"github",
"actions",
"core"
],
"homepage": "https://github.com/actions/toolkit/tree/main/packages/core",
"license": "MIT",
"main": "lib/core.js",
"types": "lib/core.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib",
"!.DS_Store"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/toolkit.git",
"directory": "packages/core"
},
"scripts": {
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
"test": "echo \"Error: run tests from root\" && exit 1",
"tsc": "tsc -p tsconfig.json"
},
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
},
"dependencies": {
"@actions/http-client": "^2.0.1",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/node": "^12.0.2",
"@types/uuid": "^8.3.4"
}
}
-21
View File
@@ -1,21 +0,0 @@
Actions Http Client for Node.js
Copyright (c) GitHub, Inc.
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-73
View File
@@ -1,73 +0,0 @@
# `@actions/http-client`
A lightweight HTTP client optimized for building actions.
## Features
- HTTP client with TypeScript generics and async/await/Promises
- Typings included!
- [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner
- Targets ES2019 (runner runs actions with node 12+). Only supported on node 12+.
- Basic, Bearer and PAT Support out of the box. Extensible handlers for others.
- Redirects supported
Features and releases [here](./RELEASES.md)
## Install
```
npm install @actions/http-client --save
```
## Samples
See the [tests](./__tests__) for detailed examples.
## Errors
### HTTP
The HTTP client does not throw unless truly exceptional.
* A request that successfully executes resulting in a 404, 500 etc... will return a response object with a status code and a body.
* Redirects (3xx) will be followed by default.
See the [tests](./__tests__) for detailed examples.
## Debugging
To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible:
```shell
export NODE_DEBUG=http
```
## Node support
The http-client is built using the latest LTS version of Node 12. It may work on previous node LTS versions but it's tested and officially supported on Node12+.
## Support and Versioning
We follow semver and will hold compatibility between major versions and increment the minor version with new features and capabilities (while holding compat).
## Contributing
We welcome PRs. Please create an issue and if applicable, a design before proceeding with code.
once:
```
npm install
```
To build:
```
npm run build
```
To run all tests:
```
npm test
```
-26
View File
@@ -1,26 +0,0 @@
/// <reference types="node" />
import * as http from 'http';
import * as ifm from './interfaces';
import { HttpClientResponse } from './index';
export declare class BasicCredentialHandler implements ifm.RequestHandler {
username: string;
password: string;
constructor(username: string, password: string);
prepareRequest(options: http.RequestOptions): void;
canHandleAuthentication(): boolean;
handleAuthentication(): Promise<HttpClientResponse>;
}
export declare class BearerCredentialHandler implements ifm.RequestHandler {
token: string;
constructor(token: string);
prepareRequest(options: http.RequestOptions): void;
canHandleAuthentication(): boolean;
handleAuthentication(): Promise<HttpClientResponse>;
}
export declare class PersonalAccessTokenCredentialHandler implements ifm.RequestHandler {
token: string;
constructor(token: string);
prepareRequest(options: http.RequestOptions): void;
canHandleAuthentication(): boolean;
handleAuthentication(): Promise<HttpClientResponse>;
}
-81
View File
@@ -1,81 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0;
class BasicCredentialHandler {
constructor(username, password) {
this.username = username;
this.password = password;
}
prepareRequest(options) {
if (!options.headers) {
throw Error('The request has no headers');
}
options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`;
}
// This handler cannot handle 401
canHandleAuthentication() {
return false;
}
handleAuthentication() {
return __awaiter(this, void 0, void 0, function* () {
throw new Error('not implemented');
});
}
}
exports.BasicCredentialHandler = BasicCredentialHandler;
class BearerCredentialHandler {
constructor(token) {
this.token = token;
}
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options) {
if (!options.headers) {
throw Error('The request has no headers');
}
options.headers['Authorization'] = `Bearer ${this.token}`;
}
// This handler cannot handle 401
canHandleAuthentication() {
return false;
}
handleAuthentication() {
return __awaiter(this, void 0, void 0, function* () {
throw new Error('not implemented');
});
}
}
exports.BearerCredentialHandler = BearerCredentialHandler;
class PersonalAccessTokenCredentialHandler {
constructor(token) {
this.token = token;
}
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options) {
if (!options.headers) {
throw Error('The request has no headers');
}
options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`;
}
// This handler cannot handle 401
canHandleAuthentication() {
return false;
}
handleAuthentication() {
return __awaiter(this, void 0, void 0, function* () {
throw new Error('not implemented');
});
}
}
exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler;
//# sourceMappingURL=auth.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":";;;;;;;;;;;;AAIA,MAAa,sBAAsB;IAIjC,YAAY,QAAgB,EAAE,QAAgB;QAC5C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED,cAAc,CAAC,OAA4B;QACzC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;YACpB,MAAM,KAAK,CAAC,4BAA4B,CAAC,CAAA;SAC1C;QACD,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,MAAM,CAAC,IAAI,CACrD,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,EAAE,CACpC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAA;IACxB,CAAC;IAED,iCAAiC;IACjC,uBAAuB;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;IAEK,oBAAoB;;YACxB,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;QACpC,CAAC;KAAA;CACF;AA1BD,wDA0BC;AAED,MAAa,uBAAuB;IAGlC,YAAY,KAAa;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;IAED,yCAAyC;IACzC,sDAAsD;IACtD,cAAc,CAAC,OAA4B;QACzC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;YACpB,MAAM,KAAK,CAAC,4BAA4B,CAAC,CAAA;SAC1C;QACD,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,IAAI,CAAC,KAAK,EAAE,CAAA;IAC3D,CAAC;IAED,iCAAiC;IACjC,uBAAuB;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;IAEK,oBAAoB;;YACxB,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;QACpC,CAAC;KAAA;CACF;AAxBD,0DAwBC;AAED,MAAa,oCAAoC;IAK/C,YAAY,KAAa;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;IACpB,CAAC;IAED,yCAAyC;IACzC,sDAAsD;IACtD,cAAc,CAAC,OAA4B;QACzC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE;YACpB,MAAM,KAAK,CAAC,4BAA4B,CAAC,CAAA;SAC1C;QACD,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,SAAS,MAAM,CAAC,IAAI,CACrD,OAAO,IAAI,CAAC,KAAK,EAAE,CACpB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAA;IACxB,CAAC;IAED,iCAAiC;IACjC,uBAAuB;QACrB,OAAO,KAAK,CAAA;IACd,CAAC;IAEK,oBAAoB;;YACxB,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAA;QACpC,CAAC;KAAA;CACF;AA5BD,oFA4BC"}
-130
View File
@@ -1,130 +0,0 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import * as http from 'http';
import * as ifm from './interfaces';
import { ProxyAgent } from 'undici';
export declare enum HttpCodes {
OK = 200,
MultipleChoices = 300,
MovedPermanently = 301,
ResourceMoved = 302,
SeeOther = 303,
NotModified = 304,
UseProxy = 305,
SwitchProxy = 306,
TemporaryRedirect = 307,
PermanentRedirect = 308,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticationRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
TooManyRequests = 429,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504
}
export declare enum Headers {
Accept = "accept",
ContentType = "content-type"
}
export declare enum MediaTypes {
ApplicationJson = "application/json"
}
/**
* Returns the proxy URL, depending upon the supplied url and proxy environment variables.
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
export declare function getProxyUrl(serverUrl: string): string;
export declare class HttpClientError extends Error {
constructor(message: string, statusCode: number);
statusCode: number;
result?: any;
}
export declare class HttpClientResponse {
constructor(message: http.IncomingMessage);
message: http.IncomingMessage;
readBody(): Promise<string>;
readBodyBuffer?(): Promise<Buffer>;
}
export declare function isHttps(requestUrl: string): boolean;
export declare class HttpClient {
userAgent: string | undefined;
handlers: ifm.RequestHandler[];
requestOptions: ifm.RequestOptions | undefined;
private _ignoreSslError;
private _socketTimeout;
private _allowRedirects;
private _allowRedirectDowngrade;
private _maxRedirects;
private _allowRetries;
private _maxRetries;
private _agent;
private _proxyAgent;
private _proxyAgentDispatcher;
private _keepAlive;
private _disposed;
constructor(userAgent?: string, handlers?: ifm.RequestHandler[], requestOptions?: ifm.RequestOptions);
options(requestUrl: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
get(requestUrl: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
del(requestUrl: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
post(requestUrl: string, data: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
patch(requestUrl: string, data: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
put(requestUrl: string, data: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
head(requestUrl: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
/**
* Gets a typed object from an endpoint
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
*/
getJson<T>(requestUrl: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<ifm.TypedResponse<T>>;
postJson<T>(requestUrl: string, obj: any, additionalHeaders?: http.OutgoingHttpHeaders): Promise<ifm.TypedResponse<T>>;
putJson<T>(requestUrl: string, obj: any, additionalHeaders?: http.OutgoingHttpHeaders): Promise<ifm.TypedResponse<T>>;
patchJson<T>(requestUrl: string, obj: any, additionalHeaders?: http.OutgoingHttpHeaders): Promise<ifm.TypedResponse<T>>;
/**
* Makes a raw http request.
* All other methods such as get, post, patch, and request ultimately call this.
* Prefer get, del, post and patch
*/
request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream | null, headers?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
/**
* Needs to be called if keepAlive is set to true in request options.
*/
dispose(): void;
/**
* Raw request.
* @param info
* @param data
*/
requestRaw(info: ifm.RequestInfo, data: string | NodeJS.ReadableStream | null): Promise<HttpClientResponse>;
/**
* Raw request with callback.
* @param info
* @param data
* @param onResult
*/
requestRawWithCallback(info: ifm.RequestInfo, data: string | NodeJS.ReadableStream | null, onResult: (err?: Error, res?: HttpClientResponse) => void): void;
/**
* Gets an http agent. This function is useful when you need an http agent that handles
* routing through a proxy server - depending upon the url and proxy environment variables.
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
getAgent(serverUrl: string): http.Agent;
getAgentDispatcher(serverUrl: string): ProxyAgent | undefined;
private _prepareRequest;
private _mergeHeaders;
private _getExistingOrDefaultHeader;
private _getAgent;
private _getProxyAgentDispatcher;
private _performExponentialBackoff;
private _processResponse;
}
-656
View File
@@ -1,656 +0,0 @@
"use strict";
/* eslint-disable @typescript-eslint/no-explicit-any */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0;
const http = __importStar(require("http"));
const https = __importStar(require("https"));
const pm = __importStar(require("./proxy"));
const tunnel = __importStar(require("tunnel"));
const undici_1 = require("undici");
var HttpCodes;
(function (HttpCodes) {
HttpCodes[HttpCodes["OK"] = 200] = "OK";
HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices";
HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently";
HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved";
HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther";
HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified";
HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy";
HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy";
HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect";
HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect";
HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest";
HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized";
HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired";
HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden";
HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound";
HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed";
HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable";
HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout";
HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict";
HttpCodes[HttpCodes["Gone"] = 410] = "Gone";
HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests";
HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError";
HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented";
HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway";
HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable";
HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout";
})(HttpCodes || (exports.HttpCodes = HttpCodes = {}));
var Headers;
(function (Headers) {
Headers["Accept"] = "accept";
Headers["ContentType"] = "content-type";
})(Headers || (exports.Headers = Headers = {}));
var MediaTypes;
(function (MediaTypes) {
MediaTypes["ApplicationJson"] = "application/json";
})(MediaTypes || (exports.MediaTypes = MediaTypes = {}));
/**
* Returns the proxy URL, depending upon the supplied url and proxy environment variables.
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
function getProxyUrl(serverUrl) {
const proxyUrl = pm.getProxyUrl(new URL(serverUrl));
return proxyUrl ? proxyUrl.href : '';
}
exports.getProxyUrl = getProxyUrl;
const HttpRedirectCodes = [
HttpCodes.MovedPermanently,
HttpCodes.ResourceMoved,
HttpCodes.SeeOther,
HttpCodes.TemporaryRedirect,
HttpCodes.PermanentRedirect
];
const HttpResponseRetryCodes = [
HttpCodes.BadGateway,
HttpCodes.ServiceUnavailable,
HttpCodes.GatewayTimeout
];
const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD'];
const ExponentialBackoffCeiling = 10;
const ExponentialBackoffTimeSlice = 5;
class HttpClientError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'HttpClientError';
this.statusCode = statusCode;
Object.setPrototypeOf(this, HttpClientError.prototype);
}
}
exports.HttpClientError = HttpClientError;
class HttpClientResponse {
constructor(message) {
this.message = message;
}
readBody() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
let output = Buffer.alloc(0);
this.message.on('data', (chunk) => {
output = Buffer.concat([output, chunk]);
});
this.message.on('end', () => {
resolve(output.toString());
});
}));
});
}
readBodyBuffer() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
const chunks = [];
this.message.on('data', (chunk) => {
chunks.push(chunk);
});
this.message.on('end', () => {
resolve(Buffer.concat(chunks));
});
}));
});
}
}
exports.HttpClientResponse = HttpClientResponse;
function isHttps(requestUrl) {
const parsedUrl = new URL(requestUrl);
return parsedUrl.protocol === 'https:';
}
exports.isHttps = isHttps;
class HttpClient {
constructor(userAgent, handlers, requestOptions) {
this._ignoreSslError = false;
this._allowRedirects = true;
this._allowRedirectDowngrade = false;
this._maxRedirects = 50;
this._allowRetries = false;
this._maxRetries = 1;
this._keepAlive = false;
this._disposed = false;
this.userAgent = userAgent;
this.handlers = handlers || [];
this.requestOptions = requestOptions;
if (requestOptions) {
if (requestOptions.ignoreSslError != null) {
this._ignoreSslError = requestOptions.ignoreSslError;
}
this._socketTimeout = requestOptions.socketTimeout;
if (requestOptions.allowRedirects != null) {
this._allowRedirects = requestOptions.allowRedirects;
}
if (requestOptions.allowRedirectDowngrade != null) {
this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade;
}
if (requestOptions.maxRedirects != null) {
this._maxRedirects = Math.max(requestOptions.maxRedirects, 0);
}
if (requestOptions.keepAlive != null) {
this._keepAlive = requestOptions.keepAlive;
}
if (requestOptions.allowRetries != null) {
this._allowRetries = requestOptions.allowRetries;
}
if (requestOptions.maxRetries != null) {
this._maxRetries = requestOptions.maxRetries;
}
}
}
options(requestUrl, additionalHeaders) {
return __awaiter(this, void 0, void 0, function* () {
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
});
}
get(requestUrl, additionalHeaders) {
return __awaiter(this, void 0, void 0, function* () {
return this.request('GET', requestUrl, null, additionalHeaders || {});
});
}
del(requestUrl, additionalHeaders) {
return __awaiter(this, void 0, void 0, function* () {
return this.request('DELETE', requestUrl, null, additionalHeaders || {});
});
}
post(requestUrl, data, additionalHeaders) {
return __awaiter(this, void 0, void 0, function* () {
return this.request('POST', requestUrl, data, additionalHeaders || {});
});
}
patch(requestUrl, data, additionalHeaders) {
return __awaiter(this, void 0, void 0, function* () {
return this.request('PATCH', requestUrl, data, additionalHeaders || {});
});
}
put(requestUrl, data, additionalHeaders) {
return __awaiter(this, void 0, void 0, function* () {
return this.request('PUT', requestUrl, data, additionalHeaders || {});
});
}
head(requestUrl, additionalHeaders) {
return __awaiter(this, void 0, void 0, function* () {
return this.request('HEAD', requestUrl, null, additionalHeaders || {});
});
}
sendStream(verb, requestUrl, stream, additionalHeaders) {
return __awaiter(this, void 0, void 0, function* () {
return this.request(verb, requestUrl, stream, additionalHeaders);
});
}
/**
* Gets a typed object from an endpoint
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
*/
getJson(requestUrl, additionalHeaders = {}) {
return __awaiter(this, void 0, void 0, function* () {
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
const res = yield this.get(requestUrl, additionalHeaders);
return this._processResponse(res, this.requestOptions);
});
}
postJson(requestUrl, obj, additionalHeaders = {}) {
return __awaiter(this, void 0, void 0, function* () {
const data = JSON.stringify(obj, null, 2);
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
const res = yield this.post(requestUrl, data, additionalHeaders);
return this._processResponse(res, this.requestOptions);
});
}
putJson(requestUrl, obj, additionalHeaders = {}) {
return __awaiter(this, void 0, void 0, function* () {
const data = JSON.stringify(obj, null, 2);
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
const res = yield this.put(requestUrl, data, additionalHeaders);
return this._processResponse(res, this.requestOptions);
});
}
patchJson(requestUrl, obj, additionalHeaders = {}) {
return __awaiter(this, void 0, void 0, function* () {
const data = JSON.stringify(obj, null, 2);
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
const res = yield this.patch(requestUrl, data, additionalHeaders);
return this._processResponse(res, this.requestOptions);
});
}
/**
* Makes a raw http request.
* All other methods such as get, post, patch, and request ultimately call this.
* Prefer get, del, post and patch
*/
request(verb, requestUrl, data, headers) {
return __awaiter(this, void 0, void 0, function* () {
if (this._disposed) {
throw new Error('Client has already been disposed.');
}
const parsedUrl = new URL(requestUrl);
let info = this._prepareRequest(verb, parsedUrl, headers);
// Only perform retries on reads since writes may not be idempotent.
const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb)
? this._maxRetries + 1
: 1;
let numTries = 0;
let response;
do {
response = yield this.requestRaw(info, data);
// Check if it's an authentication challenge
if (response &&
response.message &&
response.message.statusCode === HttpCodes.Unauthorized) {
let authenticationHandler;
for (const handler of this.handlers) {
if (handler.canHandleAuthentication(response)) {
authenticationHandler = handler;
break;
}
}
if (authenticationHandler) {
return authenticationHandler.handleAuthentication(this, info, data);
}
else {
// We have received an unauthorized response but have no handlers to handle it.
// Let the response return to the caller.
return response;
}
}
let redirectsRemaining = this._maxRedirects;
while (response.message.statusCode &&
HttpRedirectCodes.includes(response.message.statusCode) &&
this._allowRedirects &&
redirectsRemaining > 0) {
const redirectUrl = response.message.headers['location'];
if (!redirectUrl) {
// if there's no location to redirect to, we won't
break;
}
const parsedRedirectUrl = new URL(redirectUrl);
if (parsedUrl.protocol === 'https:' &&
parsedUrl.protocol !== parsedRedirectUrl.protocol &&
!this._allowRedirectDowngrade) {
throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.');
}
// we need to finish reading the response before reassigning response
// which will leak the open socket.
yield response.readBody();
// strip authorization header if redirected to a different hostname
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
for (const header in headers) {
// header names are case insensitive
if (header.toLowerCase() === 'authorization') {
delete headers[header];
}
}
}
// let's make the request with the new redirectUrl
info = this._prepareRequest(verb, parsedRedirectUrl, headers);
response = yield this.requestRaw(info, data);
redirectsRemaining--;
}
if (!response.message.statusCode ||
!HttpResponseRetryCodes.includes(response.message.statusCode)) {
// If not a retry code, return immediately instead of retrying
return response;
}
numTries += 1;
if (numTries < maxTries) {
yield response.readBody();
yield this._performExponentialBackoff(numTries);
}
} while (numTries < maxTries);
return response;
});
}
/**
* Needs to be called if keepAlive is set to true in request options.
*/
dispose() {
if (this._agent) {
this._agent.destroy();
}
this._disposed = true;
}
/**
* Raw request.
* @param info
* @param data
*/
requestRaw(info, data) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
function callbackForResult(err, res) {
if (err) {
reject(err);
}
else if (!res) {
// If `err` is not passed, then `res` must be passed.
reject(new Error('Unknown error'));
}
else {
resolve(res);
}
}
this.requestRawWithCallback(info, data, callbackForResult);
});
});
}
/**
* Raw request with callback.
* @param info
* @param data
* @param onResult
*/
requestRawWithCallback(info, data, onResult) {
if (typeof data === 'string') {
if (!info.options.headers) {
info.options.headers = {};
}
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
}
let callbackCalled = false;
function handleResult(err, res) {
if (!callbackCalled) {
callbackCalled = true;
onResult(err, res);
}
}
const req = info.httpModule.request(info.options, (msg) => {
const res = new HttpClientResponse(msg);
handleResult(undefined, res);
});
let socket;
req.on('socket', sock => {
socket = sock;
});
// If we ever get disconnected, we want the socket to timeout eventually
req.setTimeout(this._socketTimeout || 3 * 60000, () => {
if (socket) {
socket.end();
}
handleResult(new Error(`Request timeout: ${info.options.path}`));
});
req.on('error', function (err) {
// err has statusCode property
// res should have headers
handleResult(err);
});
if (data && typeof data === 'string') {
req.write(data, 'utf8');
}
if (data && typeof data !== 'string') {
data.on('close', function () {
req.end();
});
data.pipe(req);
}
else {
req.end();
}
}
/**
* Gets an http agent. This function is useful when you need an http agent that handles
* routing through a proxy server - depending upon the url and proxy environment variables.
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
getAgent(serverUrl) {
const parsedUrl = new URL(serverUrl);
return this._getAgent(parsedUrl);
}
getAgentDispatcher(serverUrl) {
const parsedUrl = new URL(serverUrl);
const proxyUrl = pm.getProxyUrl(parsedUrl);
const useProxy = proxyUrl && proxyUrl.hostname;
if (!useProxy) {
return;
}
return this._getProxyAgentDispatcher(parsedUrl, proxyUrl);
}
_prepareRequest(method, requestUrl, headers) {
const info = {};
info.parsedUrl = requestUrl;
const usingSsl = info.parsedUrl.protocol === 'https:';
info.httpModule = usingSsl ? https : http;
const defaultPort = usingSsl ? 443 : 80;
info.options = {};
info.options.host = info.parsedUrl.hostname;
info.options.port = info.parsedUrl.port
? parseInt(info.parsedUrl.port)
: defaultPort;
info.options.path =
(info.parsedUrl.pathname || '') + (info.parsedUrl.search || '');
info.options.method = method;
info.options.headers = this._mergeHeaders(headers);
if (this.userAgent != null) {
info.options.headers['user-agent'] = this.userAgent;
}
info.options.agent = this._getAgent(info.parsedUrl);
// gives handlers an opportunity to participate
if (this.handlers) {
for (const handler of this.handlers) {
handler.prepareRequest(info.options);
}
}
return info;
}
_mergeHeaders(headers) {
if (this.requestOptions && this.requestOptions.headers) {
return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {}));
}
return lowercaseKeys(headers || {});
}
_getExistingOrDefaultHeader(additionalHeaders, header, _default) {
let clientHeader;
if (this.requestOptions && this.requestOptions.headers) {
clientHeader = lowercaseKeys(this.requestOptions.headers)[header];
}
return additionalHeaders[header] || clientHeader || _default;
}
_getAgent(parsedUrl) {
let agent;
const proxyUrl = pm.getProxyUrl(parsedUrl);
const useProxy = proxyUrl && proxyUrl.hostname;
if (this._keepAlive && useProxy) {
agent = this._proxyAgent;
}
if (this._keepAlive && !useProxy) {
agent = this._agent;
}
// if agent is already assigned use that agent.
if (agent) {
return agent;
}
const usingSsl = parsedUrl.protocol === 'https:';
let maxSockets = 100;
if (this.requestOptions) {
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets;
}
// This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
if (proxyUrl && proxyUrl.hostname) {
const agentOptions = {
maxSockets,
keepAlive: this._keepAlive,
proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && {
proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
})), { host: proxyUrl.hostname, port: proxyUrl.port })
};
let tunnelAgent;
const overHttps = proxyUrl.protocol === 'https:';
if (usingSsl) {
tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp;
}
else {
tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp;
}
agent = tunnelAgent(agentOptions);
this._proxyAgent = agent;
}
// if reusing agent across request and tunneling agent isn't assigned create a new agent
if (this._keepAlive && !agent) {
const options = { keepAlive: this._keepAlive, maxSockets };
agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
this._agent = agent;
}
// if not using private agent and tunnel agent isn't setup then use global agent
if (!agent) {
agent = usingSsl ? https.globalAgent : http.globalAgent;
}
if (usingSsl && this._ignoreSslError) {
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
// we have to cast it to any and change it directly
agent.options = Object.assign(agent.options || {}, {
rejectUnauthorized: false
});
}
return agent;
}
_getProxyAgentDispatcher(parsedUrl, proxyUrl) {
let proxyAgent;
if (this._keepAlive) {
proxyAgent = this._proxyAgentDispatcher;
}
// if agent is already assigned use that agent.
if (proxyAgent) {
return proxyAgent;
}
const usingSsl = parsedUrl.protocol === 'https:';
proxyAgent = new undici_1.ProxyAgent(Object.assign({ uri: proxyUrl.href, pipelining: !this._keepAlive ? 0 : 1 }, ((proxyUrl.username || proxyUrl.password) && {
token: `${proxyUrl.username}:${proxyUrl.password}`
})));
this._proxyAgentDispatcher = proxyAgent;
if (usingSsl && this._ignoreSslError) {
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
// we have to cast it to any and change it directly
proxyAgent.options = Object.assign(proxyAgent.options.requestTls || {}, {
rejectUnauthorized: false
});
}
return proxyAgent;
}
_performExponentialBackoff(retryNumber) {
return __awaiter(this, void 0, void 0, function* () {
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
return new Promise(resolve => setTimeout(() => resolve(), ms));
});
}
_processResponse(res, options) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
const statusCode = res.message.statusCode || 0;
const response = {
statusCode,
result: null,
headers: {}
};
// not found leads to null obj returned
if (statusCode === HttpCodes.NotFound) {
resolve(response);
}
// get the result from the body
function dateTimeDeserializer(key, value) {
if (typeof value === 'string') {
const a = new Date(value);
if (!isNaN(a.valueOf())) {
return a;
}
}
return value;
}
let obj;
let contents;
try {
contents = yield res.readBody();
if (contents && contents.length > 0) {
if (options && options.deserializeDates) {
obj = JSON.parse(contents, dateTimeDeserializer);
}
else {
obj = JSON.parse(contents);
}
response.result = obj;
}
response.headers = res.message.headers;
}
catch (err) {
// Invalid resource (contents not json); leaving result obj null
}
// note that 3xx redirects are handled by the http layer.
if (statusCode > 299) {
let msg;
// if exception/error in body, attempt to get better error
if (obj && obj.message) {
msg = obj.message;
}
else if (contents && contents.length > 0) {
// it may be the case that the exception is in the body message as string
msg = contents;
}
else {
msg = `Failed request: (${statusCode})`;
}
const err = new HttpClientError(msg, statusCode);
err.result = response.result;
reject(err);
}
else {
resolve(response);
}
}));
});
}
}
exports.HttpClient = HttpClient;
const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
//# sourceMappingURL=index.js.map
File diff suppressed because one or more lines are too long
-46
View File
@@ -1,46 +0,0 @@
/// <reference types="node" />
/// <reference types="node" />
/// <reference types="node" />
import * as http from 'http';
import * as https from 'https';
import { HttpClientResponse } from './index';
export interface HttpClient {
options(requestUrl: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
get(requestUrl: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
del(requestUrl: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
post(requestUrl: string, data: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
patch(requestUrl: string, data: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
put(requestUrl: string, data: string, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
sendStream(verb: string, requestUrl: string, stream: NodeJS.ReadableStream, additionalHeaders?: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
request(verb: string, requestUrl: string, data: string | NodeJS.ReadableStream, headers: http.OutgoingHttpHeaders): Promise<HttpClientResponse>;
requestRaw(info: RequestInfo, data: string | NodeJS.ReadableStream): Promise<HttpClientResponse>;
requestRawWithCallback(info: RequestInfo, data: string | NodeJS.ReadableStream, onResult: (err?: Error, res?: HttpClientResponse) => void): void;
}
export interface RequestHandler {
prepareRequest(options: http.RequestOptions): void;
canHandleAuthentication(response: HttpClientResponse): boolean;
handleAuthentication(httpClient: HttpClient, requestInfo: RequestInfo, data: string | NodeJS.ReadableStream | null): Promise<HttpClientResponse>;
}
export interface RequestInfo {
options: http.RequestOptions;
parsedUrl: URL;
httpModule: typeof http | typeof https;
}
export interface RequestOptions {
headers?: http.OutgoingHttpHeaders;
socketTimeout?: number;
ignoreSslError?: boolean;
allowRedirects?: boolean;
allowRedirectDowngrade?: boolean;
maxRedirects?: number;
maxSockets?: number;
keepAlive?: boolean;
deserializeDates?: boolean;
allowRetries?: boolean;
maxRetries?: number;
}
export interface TypedResponse<T> {
statusCode: number;
result: T | null;
headers: http.IncomingHttpHeaders;
}
-3
View File
@@ -1,3 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=interfaces.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":""}
-2
View File
@@ -1,2 +0,0 @@
export declare function getProxyUrl(reqUrl: URL): URL | undefined;
export declare function checkBypass(reqUrl: URL): boolean;
-82
View File
@@ -1,82 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkBypass = exports.getProxyUrl = void 0;
function getProxyUrl(reqUrl) {
const usingSsl = reqUrl.protocol === 'https:';
if (checkBypass(reqUrl)) {
return undefined;
}
const proxyVar = (() => {
if (usingSsl) {
return process.env['https_proxy'] || process.env['HTTPS_PROXY'];
}
else {
return process.env['http_proxy'] || process.env['HTTP_PROXY'];
}
})();
if (proxyVar) {
try {
return new URL(proxyVar);
}
catch (_a) {
if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://'))
return new URL(`http://${proxyVar}`);
}
}
else {
return undefined;
}
}
exports.getProxyUrl = getProxyUrl;
function checkBypass(reqUrl) {
if (!reqUrl.hostname) {
return false;
}
const reqHost = reqUrl.hostname;
if (isLoopbackAddress(reqHost)) {
return true;
}
const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
if (!noProxy) {
return false;
}
// Determine the request port
let reqPort;
if (reqUrl.port) {
reqPort = Number(reqUrl.port);
}
else if (reqUrl.protocol === 'http:') {
reqPort = 80;
}
else if (reqUrl.protocol === 'https:') {
reqPort = 443;
}
// Format the request hostname and hostname with port
const upperReqHosts = [reqUrl.hostname.toUpperCase()];
if (typeof reqPort === 'number') {
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`);
}
// Compare request host against noproxy
for (const upperNoProxyItem of noProxy
.split(',')
.map(x => x.trim().toUpperCase())
.filter(x => x)) {
if (upperNoProxyItem === '*' ||
upperReqHosts.some(x => x === upperNoProxyItem ||
x.endsWith(`.${upperNoProxyItem}`) ||
(upperNoProxyItem.startsWith('.') &&
x.endsWith(`${upperNoProxyItem}`)))) {
return true;
}
}
return false;
}
exports.checkBypass = checkBypass;
function isLoopbackAddress(host) {
const hostLower = host.toLowerCase();
return (hostLower === 'localhost' ||
hostLower.startsWith('127.') ||
hostLower.startsWith('[::1]') ||
hostLower.startsWith('[0:0:0:0:0:0:0:1]'));
}
//# sourceMappingURL=proxy.js.map
-1
View File
@@ -1 +0,0 @@
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":";;;AAAA,SAAgB,WAAW,CAAC,MAAW;IACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAA;IAE7C,IAAI,WAAW,CAAC,MAAM,CAAC,EAAE;QACvB,OAAO,SAAS,CAAA;KACjB;IAED,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE;QACrB,IAAI,QAAQ,EAAE;YACZ,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;SAChE;aAAM;YACL,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;SAC9D;IACH,CAAC,CAAC,EAAE,CAAA;IAEJ,IAAI,QAAQ,EAAE;QACZ,IAAI;YACF,OAAO,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAA;SACzB;QAAC,WAAM;YACN,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,CAAC;gBACrE,OAAO,IAAI,GAAG,CAAC,UAAU,QAAQ,EAAE,CAAC,CAAA;SACvC;KACF;SAAM;QACL,OAAO,SAAS,CAAA;KACjB;AACH,CAAC;AAzBD,kCAyBC;AAED,SAAgB,WAAW,CAAC,MAAW;IACrC,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE;QACpB,OAAO,KAAK,CAAA;KACb;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAA;IAC/B,IAAI,iBAAiB,CAAC,OAAO,CAAC,EAAE;QAC9B,OAAO,IAAI,CAAA;KACZ;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAA;IACxE,IAAI,CAAC,OAAO,EAAE;QACZ,OAAO,KAAK,CAAA;KACb;IAED,6BAA6B;IAC7B,IAAI,OAA2B,CAAA;IAC/B,IAAI,MAAM,CAAC,IAAI,EAAE;QACf,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;KAC9B;SAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE;QACtC,OAAO,GAAG,EAAE,CAAA;KACb;SAAM,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE;QACvC,OAAO,GAAG,GAAG,CAAA;KACd;IAED,qDAAqD;IACrD,MAAM,aAAa,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IACrD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE;QAC/B,aAAa,CAAC,IAAI,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC,CAAA;KACrD;IAED,uCAAuC;IACvC,KAAK,MAAM,gBAAgB,IAAI,OAAO;SACnC,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;QACjB,IACE,gBAAgB,KAAK,GAAG;YACxB,aAAa,CAAC,IAAI,CAChB,CAAC,CAAC,EAAE,CACF,CAAC,KAAK,gBAAgB;gBACtB,CAAC,CAAC,QAAQ,CAAC,IAAI,gBAAgB,EAAE,CAAC;gBAClC,CAAC,gBAAgB,CAAC,UAAU,CAAC,GAAG,CAAC;oBAC/B,CAAC,CAAC,QAAQ,CAAC,GAAG,gBAAgB,EAAE,CAAC,CAAC,CACvC,EACD;YACA,OAAO,IAAI,CAAA;SACZ;KACF;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAnDD,kCAmDC;AAED,SAAS,iBAAiB,CAAC,IAAY;IACrC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAA;IACpC,OAAO,CACL,SAAS,KAAK,WAAW;QACzB,SAAS,CAAC,UAAU,CAAC,MAAM,CAAC;QAC5B,SAAS,CAAC,UAAU,CAAC,OAAO,CAAC;QAC7B,SAAS,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAC1C,CAAA;AACH,CAAC"}
-51
View File
@@ -1,51 +0,0 @@
{
"name": "@actions/http-client",
"version": "2.2.0",
"description": "Actions Http Client",
"keywords": [
"github",
"actions",
"http"
],
"homepage": "https://github.com/actions/toolkit/tree/main/packages/http-client",
"license": "MIT",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib",
"!.DS_Store"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/toolkit.git",
"directory": "packages/http-client"
},
"scripts": {
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
"test": "echo \"Error: run tests from root\" && exit 1",
"build": "tsc",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"tsc": "tsc"
},
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
},
"devDependencies": {
"@types/node": "20.7.1",
"@types/tunnel": "0.0.3",
"proxy": "^2.1.1",
"@types/proxy": "^1.0.1"
},
"dependencies": {
"tunnel": "^0.0.6",
"undici": "^5.25.4"
}
}
-19
View File
@@ -1,19 +0,0 @@
Copyright Brian White. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-271
View File
@@ -1,271 +0,0 @@
# busboy
<div align="center">
[![Build Status](https://github.com/fastify/busboy/workflows/ci/badge.svg)](https://github.com/fastify/busboy/actions)
[![Coverage Status](https://coveralls.io/repos/fastify/busboy/badge.svg?branch=master)](https://coveralls.io/r/fastify/busboy?branch=master)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://standardjs.com/)
[![Security Responsible Disclosure](https://img.shields.io/badge/Security-Responsible%20Disclosure-yellow.svg)](https://github.com/nodejs/security-wg/blob/HEAD/processes/responsible_disclosure_template.md)
</div>
<div align="center">
[![NPM version](https://img.shields.io/npm/v/@fastify/busboy.svg?style=flat)](https://www.npmjs.com/package/@fastify/busboy)
[![NPM downloads](https://img.shields.io/npm/dm/@fastify/busboy.svg?style=flat)](https://www.npmjs.com/package/@fastify/busboy)
</div>
Description
===========
A Node.js module for parsing incoming HTML form data.
This is an officially supported fork by [fastify](https://github.com/fastify/) organization of the amazing library [originally created](https://github.com/mscdex/busboy) by Brian White,
aimed at addressing long-standing issues with it.
Benchmark (Mean time for 500 Kb payload, 2000 cycles, 1000 cycle warmup):
| Library | Version | Mean time in nanoseconds (less is better) |
|-----------------------|---------|-------------------------------------------|
| busboy | 0.3.1 | `340114` |
| @fastify/busboy | 1.0.0 | `270984` |
[Changelog](https://github.com/fastify/busboy/blob/master/CHANGELOG.md) since busboy 0.31.
Requirements
============
* [Node.js](http://nodejs.org/) 10+
Install
=======
npm i @fastify/busboy
Examples
========
* Parsing (multipart) with default options:
```javascript
const http = require('node:http');
const { inspect } = require('node:util');
const Busboy = require('busboy');
http.createServer((req, res) => {
if (req.method === 'POST') {
const busboy = new Busboy({ headers: req.headers });
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
console.log(`File [${fieldname}]: filename: ${filename}, encoding: ${encoding}, mimetype: ${mimetype}`);
file.on('data', data => {
console.log(`File [${fieldname}] got ${data.length} bytes`);
});
file.on('end', () => {
console.log(`File [${fieldname}] Finished`);
});
});
busboy.on('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {
console.log(`Field [${fieldname}]: value: ${inspect(val)}`);
});
busboy.on('finish', () => {
console.log('Done parsing form!');
res.writeHead(303, { Connection: 'close', Location: '/' });
res.end();
});
req.pipe(busboy);
} else if (req.method === 'GET') {
res.writeHead(200, { Connection: 'close' });
res.end(`<html><head></head><body>
<form method="POST" enctype="multipart/form-data">
<input type="text" name="textfield"><br>
<input type="file" name="filefield"><br>
<input type="submit">
</form>
</body></html>`);
}
}).listen(8000, () => {
console.log('Listening for requests');
});
// Example output, using http://nodejs.org/images/ryan-speaker.jpg as the file:
//
// Listening for requests
// File [filefield]: filename: ryan-speaker.jpg, encoding: binary
// File [filefield] got 11971 bytes
// Field [textfield]: value: 'testing! :-)'
// File [filefield] Finished
// Done parsing form!
```
* Save all incoming files to disk:
```javascript
const http = require('node:http');
const path = require('node:path');
const os = require('node:os');
const fs = require('node:fs');
const Busboy = require('busboy');
http.createServer(function(req, res) {
if (req.method === 'POST') {
const busboy = new Busboy({ headers: req.headers });
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
var saveTo = path.join(os.tmpdir(), path.basename(fieldname));
file.pipe(fs.createWriteStream(saveTo));
});
busboy.on('finish', function() {
res.writeHead(200, { 'Connection': 'close' });
res.end("That's all folks!");
});
return req.pipe(busboy);
}
res.writeHead(404);
res.end();
}).listen(8000, function() {
console.log('Listening for requests');
});
```
* Parsing (urlencoded) with default options:
```javascript
const http = require('node:http');
const { inspect } = require('node:util');
const Busboy = require('busboy');
http.createServer(function(req, res) {
if (req.method === 'POST') {
const busboy = new Busboy({ headers: req.headers });
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
console.log('File [' + fieldname + ']: filename: ' + filename);
file.on('data', function(data) {
console.log('File [' + fieldname + '] got ' + data.length + ' bytes');
});
file.on('end', function() {
console.log('File [' + fieldname + '] Finished');
});
});
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
console.log('Field [' + fieldname + ']: value: ' + inspect(val));
});
busboy.on('finish', function() {
console.log('Done parsing form!');
res.writeHead(303, { Connection: 'close', Location: '/' });
res.end();
});
req.pipe(busboy);
} else if (req.method === 'GET') {
res.writeHead(200, { Connection: 'close' });
res.end('<html><head></head><body>\
<form method="POST">\
<input type="text" name="textfield"><br />\
<select name="selectfield">\
<option value="1">1</option>\
<option value="10">10</option>\
<option value="100">100</option>\
<option value="9001">9001</option>\
</select><br />\
<input type="checkbox" name="checkfield">Node.js rules!<br />\
<input type="submit">\
</form>\
</body></html>');
}
}).listen(8000, function() {
console.log('Listening for requests');
});
// Example output:
//
// Listening for requests
// Field [textfield]: value: 'testing! :-)'
// Field [selectfield]: value: '9001'
// Field [checkfield]: value: 'on'
// Done parsing form!
```
API
===
_Busboy_ is a _Writable_ stream
Busboy (special) events
-----------------------
* **file**(< _string_ >fieldname, < _ReadableStream_ >stream, < _string_ >filename, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new file form field found. `transferEncoding` contains the 'Content-Transfer-Encoding' value for the file stream. `mimeType` contains the 'Content-Type' value for the file stream.
* Note: if you listen for this event, you should always handle the `stream` no matter if you care about the file contents or not (e.g. you can simply just do `stream.resume();` if you want to discard the contents), otherwise the 'finish' event will never fire on the Busboy instance. However, if you don't care about **any** incoming files, you can simply not listen for the 'file' event at all and any/all files will be automatically and safely discarded (these discarded files do still count towards `files` and `parts` limits).
* If a configured file size limit was reached, `stream` will both have a boolean property `truncated` (best checked at the end of the stream) and emit a 'limit' event to notify you when this happens.
* The property `bytesRead` informs about the number of bytes that have been read so far.
* **field**(< _string_ >fieldname, < _string_ >value, < _boolean_ >fieldnameTruncated, < _boolean_ >valueTruncated, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new non-file field found.
* **partsLimit**() - Emitted when specified `parts` limit has been reached. No more 'file' or 'field' events will be emitted.
* **filesLimit**() - Emitted when specified `files` limit has been reached. No more 'file' events will be emitted.
* **fieldsLimit**() - Emitted when specified `fields` limit has been reached. No more 'field' events will be emitted.
Busboy methods
--------------
* **(constructor)**(< _object_ >config) - Creates and returns a new Busboy instance.
* The constructor takes the following valid `config` settings:
* **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers.
* **autoDestroy** - _boolean_ - Whether this stream should automatically call .destroy() on itself after ending. (Default: false).
* **highWaterMark** - _integer_ - highWaterMark to use for this Busboy instance (Default: WritableStream default).
* **fileHwm** - _integer_ - highWaterMark to use for file streams (Default: ReadableStream default).
* **defCharset** - _string_ - Default character set to use when one isn't defined (Default: 'utf8').
* **preservePath** - _boolean_ - If paths in the multipart 'filename' field shall be preserved. (Default: false).
* **isPartAFile** - __function__ - Use this function to override the default file detection functionality. It has following parameters:
* fieldName - __string__ The name of the field.
* contentType - __string__ The content-type of the part, e.g. `text/plain`, `image/jpeg`, `application/octet-stream`
* fileName - __string__ The name of a file supplied by the part.
(Default: `(fieldName, contentType, fileName) => (contentType === 'application/octet-stream' || fileName !== undefined)`)
* **limits** - _object_ - Various limits on incoming data. Valid properties are:
* **fieldNameSize** - _integer_ - Max field name size (in bytes) (Default: 100 bytes).
* **fieldSize** - _integer_ - Max field value size (in bytes) (Default: 1 MiB, which is 1024 x 1024 bytes).
* **fields** - _integer_ - Max number of non-file fields (Default: Infinity).
* **fileSize** - _integer_ - For multipart forms, the max file size (in bytes) (Default: Infinity).
* **files** - _integer_ - For multipart forms, the max number of file fields (Default: Infinity).
* **parts** - _integer_ - For multipart forms, the max number of parts (fields + files) (Default: Infinity).
* **headerPairs** - _integer_ - For multipart forms, the max number of header key=>value pairs to parse **Default:** 2000
* **headerSize** - _integer_ - For multipart forms, the max size of a multipart header **Default:** 81920.
* The constructor can throw errors:
* **Busboy expected an options-Object.** - Busboy expected an Object as first parameters.
* **Busboy expected an options-Object with headers-attribute.** - The first parameter is lacking of a headers-attribute.
* **Limit $limit is not a valid number** - Busboy expected the desired limit to be of type number. Busboy throws this Error to prevent a potential security issue by falling silently back to the Busboy-defaults. Potential source for this Error can be the direct use of environment variables without transforming them to the type number.
* **Unsupported Content-Type.** - The `Content-Type` isn't one Busboy can parse.
* **Missing Content-Type-header.** - The provided headers don't include `Content-Type` at all.
-19
View File
@@ -1,19 +0,0 @@
Copyright Brian White. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
-207
View File
@@ -1,207 +0,0 @@
'use strict'
const WritableStream = require('node:stream').Writable
const inherits = require('node:util').inherits
const StreamSearch = require('../../streamsearch/sbmh')
const PartStream = require('./PartStream')
const HeaderParser = require('./HeaderParser')
const DASH = 45
const B_ONEDASH = Buffer.from('-')
const B_CRLF = Buffer.from('\r\n')
const EMPTY_FN = function () {}
function Dicer (cfg) {
if (!(this instanceof Dicer)) { return new Dicer(cfg) }
WritableStream.call(this, cfg)
if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) { throw new TypeError('Boundary required') }
if (typeof cfg.boundary === 'string') { this.setBoundary(cfg.boundary) } else { this._bparser = undefined }
this._headerFirst = cfg.headerFirst
this._dashes = 0
this._parts = 0
this._finished = false
this._realFinish = false
this._isPreamble = true
this._justMatched = false
this._firstWrite = true
this._inHeader = true
this._part = undefined
this._cb = undefined
this._ignoreData = false
this._partOpts = { highWaterMark: cfg.partHwm }
this._pause = false
const self = this
this._hparser = new HeaderParser(cfg)
this._hparser.on('header', function (header) {
self._inHeader = false
self._part.emit('header', header)
})
}
inherits(Dicer, WritableStream)
Dicer.prototype.emit = function (ev) {
if (ev === 'finish' && !this._realFinish) {
if (!this._finished) {
const self = this
process.nextTick(function () {
self.emit('error', new Error('Unexpected end of multipart data'))
if (self._part && !self._ignoreData) {
const type = (self._isPreamble ? 'Preamble' : 'Part')
self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data'))
self._part.push(null)
process.nextTick(function () {
self._realFinish = true
self.emit('finish')
self._realFinish = false
})
return
}
self._realFinish = true
self.emit('finish')
self._realFinish = false
})
}
} else { WritableStream.prototype.emit.apply(this, arguments) }
}
Dicer.prototype._write = function (data, encoding, cb) {
// ignore unexpected data (e.g. extra trailer data after finished)
if (!this._hparser && !this._bparser) { return cb() }
if (this._headerFirst && this._isPreamble) {
if (!this._part) {
this._part = new PartStream(this._partOpts)
if (this._events.preamble) { this.emit('preamble', this._part) } else { this._ignore() }
}
const r = this._hparser.push(data)
if (!this._inHeader && r !== undefined && r < data.length) { data = data.slice(r) } else { return cb() }
}
// allows for "easier" testing
if (this._firstWrite) {
this._bparser.push(B_CRLF)
this._firstWrite = false
}
this._bparser.push(data)
if (this._pause) { this._cb = cb } else { cb() }
}
Dicer.prototype.reset = function () {
this._part = undefined
this._bparser = undefined
this._hparser = undefined
}
Dicer.prototype.setBoundary = function (boundary) {
const self = this
this._bparser = new StreamSearch('\r\n--' + boundary)
this._bparser.on('info', function (isMatch, data, start, end) {
self._oninfo(isMatch, data, start, end)
})
}
Dicer.prototype._ignore = function () {
if (this._part && !this._ignoreData) {
this._ignoreData = true
this._part.on('error', EMPTY_FN)
// we must perform some kind of read on the stream even though we are
// ignoring the data, otherwise node's Readable stream will not emit 'end'
// after pushing null to the stream
this._part.resume()
}
}
Dicer.prototype._oninfo = function (isMatch, data, start, end) {
let buf; const self = this; let i = 0; let r; let shouldWriteMore = true
if (!this._part && this._justMatched && data) {
while (this._dashes < 2 && (start + i) < end) {
if (data[start + i] === DASH) {
++i
++this._dashes
} else {
if (this._dashes) { buf = B_ONEDASH }
this._dashes = 0
break
}
}
if (this._dashes === 2) {
if ((start + i) < end && this._events.trailer) { this.emit('trailer', data.slice(start + i, end)) }
this.reset()
this._finished = true
// no more parts will be added
if (self._parts === 0) {
self._realFinish = true
self.emit('finish')
self._realFinish = false
}
}
if (this._dashes) { return }
}
if (this._justMatched) { this._justMatched = false }
if (!this._part) {
this._part = new PartStream(this._partOpts)
this._part._read = function (n) {
self._unpause()
}
if (this._isPreamble && this._events.preamble) { this.emit('preamble', this._part) } else if (this._isPreamble !== true && this._events.part) { this.emit('part', this._part) } else { this._ignore() }
if (!this._isPreamble) { this._inHeader = true }
}
if (data && start < end && !this._ignoreData) {
if (this._isPreamble || !this._inHeader) {
if (buf) { shouldWriteMore = this._part.push(buf) }
shouldWriteMore = this._part.push(data.slice(start, end))
if (!shouldWriteMore) { this._pause = true }
} else if (!this._isPreamble && this._inHeader) {
if (buf) { this._hparser.push(buf) }
r = this._hparser.push(data.slice(start, end))
if (!this._inHeader && r !== undefined && r < end) { this._oninfo(false, data, start + r, end) }
}
}
if (isMatch) {
this._hparser.reset()
if (this._isPreamble) { this._isPreamble = false } else {
if (start !== end) {
++this._parts
this._part.on('end', function () {
if (--self._parts === 0) {
if (self._finished) {
self._realFinish = true
self.emit('finish')
self._realFinish = false
} else {
self._unpause()
}
}
})
}
}
this._part.push(null)
this._part = undefined
this._ignoreData = false
this._justMatched = true
this._dashes = 0
}
}
Dicer.prototype._unpause = function () {
if (!this._pause) { return }
this._pause = false
if (this._cb) {
const cb = this._cb
this._cb = undefined
cb()
}
}
module.exports = Dicer
-100
View File
@@ -1,100 +0,0 @@
'use strict'
const EventEmitter = require('node:events').EventEmitter
const inherits = require('node:util').inherits
const getLimit = require('../../../lib/utils/getLimit')
const StreamSearch = require('../../streamsearch/sbmh')
const B_DCRLF = Buffer.from('\r\n\r\n')
const RE_CRLF = /\r\n/g
const RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/ // eslint-disable-line no-control-regex
function HeaderParser (cfg) {
EventEmitter.call(this)
cfg = cfg || {}
const self = this
this.nread = 0
this.maxed = false
this.npairs = 0
this.maxHeaderPairs = getLimit(cfg, 'maxHeaderPairs', 2000)
this.maxHeaderSize = getLimit(cfg, 'maxHeaderSize', 80 * 1024)
this.buffer = ''
this.header = {}
this.finished = false
this.ss = new StreamSearch(B_DCRLF)
this.ss.on('info', function (isMatch, data, start, end) {
if (data && !self.maxed) {
if (self.nread + end - start >= self.maxHeaderSize) {
end = self.maxHeaderSize - self.nread + start
self.nread = self.maxHeaderSize
self.maxed = true
} else { self.nread += (end - start) }
self.buffer += data.toString('binary', start, end)
}
if (isMatch) { self._finish() }
})
}
inherits(HeaderParser, EventEmitter)
HeaderParser.prototype.push = function (data) {
const r = this.ss.push(data)
if (this.finished) { return r }
}
HeaderParser.prototype.reset = function () {
this.finished = false
this.buffer = ''
this.header = {}
this.ss.reset()
}
HeaderParser.prototype._finish = function () {
if (this.buffer) { this._parseHeader() }
this.ss.matches = this.ss.maxMatches
const header = this.header
this.header = {}
this.buffer = ''
this.finished = true
this.nread = this.npairs = 0
this.maxed = false
this.emit('header', header)
}
HeaderParser.prototype._parseHeader = function () {
if (this.npairs === this.maxHeaderPairs) { return }
const lines = this.buffer.split(RE_CRLF)
const len = lines.length
let m, h
for (var i = 0; i < len; ++i) { // eslint-disable-line no-var
if (lines[i].length === 0) { continue }
if (lines[i][0] === '\t' || lines[i][0] === ' ') {
// folded header content
// RFC2822 says to just remove the CRLF and not the whitespace following
// it, so we follow the RFC and include the leading whitespace ...
if (h) {
this.header[h][this.header[h].length - 1] += lines[i]
continue
}
}
const posColon = lines[i].indexOf(':')
if (
posColon === -1 ||
posColon === 0
) {
return
}
m = RE_HDR.exec(lines[i])
h = m[1].toLowerCase()
this.header[h] = this.header[h] || []
this.header[h].push((m[2] || ''))
if (++this.npairs === this.maxHeaderPairs) { break }
}
}
module.exports = HeaderParser
-13
View File
@@ -1,13 +0,0 @@
'use strict'
const inherits = require('node:util').inherits
const ReadableStream = require('node:stream').Readable
function PartStream (opts) {
ReadableStream.call(this, opts)
}
inherits(PartStream, ReadableStream)
PartStream.prototype._read = function (n) {}
module.exports = PartStream
-164
View File
@@ -1,164 +0,0 @@
// Type definitions for dicer 0.2
// Project: https://github.com/mscdex/dicer
// Definitions by: BendingBender <https://github.com/BendingBender>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 2.2
/// <reference types="node" />
import stream = require("stream");
// tslint:disable:unified-signatures
/**
* A very fast streaming multipart parser for node.js.
* Dicer is a WritableStream
*
* Dicer (special) events:
* - on('finish', ()) - Emitted when all parts have been parsed and the Dicer instance has been ended.
* - on('part', (stream: PartStream)) - Emitted when a new part has been found.
* - on('preamble', (stream: PartStream)) - Emitted for preamble if you should happen to need it (can usually be ignored).
* - on('trailer', (data: Buffer)) - Emitted when trailing data was found after the terminating boundary (as with the preamble, this can usually be ignored too).
*/
export class Dicer extends stream.Writable {
/**
* Creates and returns a new Dicer instance with the following valid config settings:
*
* @param config The configuration to use
*/
constructor(config: Dicer.Config);
/**
* Sets the boundary to use for parsing and performs some initialization needed for parsing.
* You should only need to use this if you set headerFirst to true in the constructor and are parsing the boundary from the preamble header.
*
* @param boundary The boundary to use
*/
setBoundary(boundary: string): void;
addListener(event: "finish", listener: () => void): this;
addListener(event: "part", listener: (stream: Dicer.PartStream) => void): this;
addListener(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
addListener(event: "trailer", listener: (data: Buffer) => void): this;
addListener(event: "close", listener: () => void): this;
addListener(event: "drain", listener: () => void): this;
addListener(event: "error", listener: (err: Error) => void): this;
addListener(event: "pipe", listener: (src: stream.Readable) => void): this;
addListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
addListener(event: string, listener: (...args: any[]) => void): this;
on(event: "finish", listener: () => void): this;
on(event: "part", listener: (stream: Dicer.PartStream) => void): this;
on(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
on(event: "trailer", listener: (data: Buffer) => void): this;
on(event: "close", listener: () => void): this;
on(event: "drain", listener: () => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: "pipe", listener: (src: stream.Readable) => void): this;
on(event: "unpipe", listener: (src: stream.Readable) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
once(event: "finish", listener: () => void): this;
once(event: "part", listener: (stream: Dicer.PartStream) => void): this;
once(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
once(event: "trailer", listener: (data: Buffer) => void): this;
once(event: "close", listener: () => void): this;
once(event: "drain", listener: () => void): this;
once(event: "error", listener: (err: Error) => void): this;
once(event: "pipe", listener: (src: stream.Readable) => void): this;
once(event: "unpipe", listener: (src: stream.Readable) => void): this;
once(event: string, listener: (...args: any[]) => void): this;
prependListener(event: "finish", listener: () => void): this;
prependListener(event: "part", listener: (stream: Dicer.PartStream) => void): this;
prependListener(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
prependListener(event: "trailer", listener: (data: Buffer) => void): this;
prependListener(event: "close", listener: () => void): this;
prependListener(event: "drain", listener: () => void): this;
prependListener(event: "error", listener: (err: Error) => void): this;
prependListener(event: "pipe", listener: (src: stream.Readable) => void): this;
prependListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
prependListener(event: string, listener: (...args: any[]) => void): this;
prependOnceListener(event: "finish", listener: () => void): this;
prependOnceListener(event: "part", listener: (stream: Dicer.PartStream) => void): this;
prependOnceListener(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
prependOnceListener(event: "trailer", listener: (data: Buffer) => void): this;
prependOnceListener(event: "close", listener: () => void): this;
prependOnceListener(event: "drain", listener: () => void): this;
prependOnceListener(event: "error", listener: (err: Error) => void): this;
prependOnceListener(event: "pipe", listener: (src: stream.Readable) => void): this;
prependOnceListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
removeListener(event: "finish", listener: () => void): this;
removeListener(event: "part", listener: (stream: Dicer.PartStream) => void): this;
removeListener(event: "preamble", listener: (stream: Dicer.PartStream) => void): this;
removeListener(event: "trailer", listener: (data: Buffer) => void): this;
removeListener(event: "close", listener: () => void): this;
removeListener(event: "drain", listener: () => void): this;
removeListener(event: "error", listener: (err: Error) => void): this;
removeListener(event: "pipe", listener: (src: stream.Readable) => void): this;
removeListener(event: "unpipe", listener: (src: stream.Readable) => void): this;
removeListener(event: string, listener: (...args: any[]) => void): this;
}
declare namespace Dicer {
interface Config {
/**
* This is the boundary used to detect the beginning of a new part.
*/
boundary?: string | undefined;
/**
* If true, preamble header parsing will be performed first.
*/
headerFirst?: boolean | undefined;
/**
* The maximum number of header key=>value pairs to parse Default: 2000 (same as node's http).
*/
maxHeaderPairs?: number | undefined;
}
/**
* PartStream is a _ReadableStream_
*
* PartStream (special) events:
* - on('header', (header: object)) - An object containing the header for this particular part. Each property value is an array of one or more string values.
*/
interface PartStream extends stream.Readable {
addListener(event: "header", listener: (header: object) => void): this;
addListener(event: "close", listener: () => void): this;
addListener(event: "data", listener: (chunk: Buffer | string) => void): this;
addListener(event: "end", listener: () => void): this;
addListener(event: "readable", listener: () => void): this;
addListener(event: "error", listener: (err: Error) => void): this;
addListener(event: string, listener: (...args: any[]) => void): this;
on(event: "header", listener: (header: object) => void): this;
on(event: "close", listener: () => void): this;
on(event: "data", listener: (chunk: Buffer | string) => void): this;
on(event: "end", listener: () => void): this;
on(event: "readable", listener: () => void): this;
on(event: "error", listener: (err: Error) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
once(event: "header", listener: (header: object) => void): this;
once(event: "close", listener: () => void): this;
once(event: "data", listener: (chunk: Buffer | string) => void): this;
once(event: "end", listener: () => void): this;
once(event: "readable", listener: () => void): this;
once(event: "error", listener: (err: Error) => void): this;
once(event: string, listener: (...args: any[]) => void): this;
prependListener(event: "header", listener: (header: object) => void): this;
prependListener(event: "close", listener: () => void): this;
prependListener(event: "data", listener: (chunk: Buffer | string) => void): this;
prependListener(event: "end", listener: () => void): this;
prependListener(event: "readable", listener: () => void): this;
prependListener(event: "error", listener: (err: Error) => void): this;
prependListener(event: string, listener: (...args: any[]) => void): this;
prependOnceListener(event: "header", listener: (header: object) => void): this;
prependOnceListener(event: "close", listener: () => void): this;
prependOnceListener(event: "data", listener: (chunk: Buffer | string) => void): this;
prependOnceListener(event: "end", listener: () => void): this;
prependOnceListener(event: "readable", listener: () => void): this;
prependOnceListener(event: "error", listener: (err: Error) => void): this;
prependOnceListener(event: string, listener: (...args: any[]) => void): this;
removeListener(event: "header", listener: (header: object) => void): this;
removeListener(event: "close", listener: () => void): this;
removeListener(event: "data", listener: (chunk: Buffer | string) => void): this;
removeListener(event: "end", listener: () => void): this;
removeListener(event: "readable", listener: () => void): this;
removeListener(event: "error", listener: (err: Error) => void): this;
removeListener(event: string, listener: (...args: any[]) => void): this;
}
}
-228
View File
@@ -1,228 +0,0 @@
'use strict'
/**
* Copyright Brian White. All rights reserved.
*
* @see https://github.com/mscdex/streamsearch
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* Based heavily on the Streaming Boyer-Moore-Horspool C++ implementation
* by Hongli Lai at: https://github.com/FooBarWidget/boyer-moore-horspool
*/
const EventEmitter = require('node:events').EventEmitter
const inherits = require('node:util').inherits
function SBMH (needle) {
if (typeof needle === 'string') {
needle = Buffer.from(needle)
}
if (!Buffer.isBuffer(needle)) {
throw new TypeError('The needle has to be a String or a Buffer.')
}
const needleLength = needle.length
if (needleLength === 0) {
throw new Error('The needle cannot be an empty String/Buffer.')
}
if (needleLength > 256) {
throw new Error('The needle cannot have a length bigger than 256.')
}
this.maxMatches = Infinity
this.matches = 0
this._occ = new Array(256)
.fill(needleLength) // Initialize occurrence table.
this._lookbehind_size = 0
this._needle = needle
this._bufpos = 0
this._lookbehind = Buffer.alloc(needleLength)
// Populate occurrence table with analysis of the needle,
// ignoring last letter.
for (var i = 0; i < needleLength - 1; ++i) { // eslint-disable-line no-var
this._occ[needle[i]] = needleLength - 1 - i
}
}
inherits(SBMH, EventEmitter)
SBMH.prototype.reset = function () {
this._lookbehind_size = 0
this.matches = 0
this._bufpos = 0
}
SBMH.prototype.push = function (chunk, pos) {
if (!Buffer.isBuffer(chunk)) {
chunk = Buffer.from(chunk, 'binary')
}
const chlen = chunk.length
this._bufpos = pos || 0
let r
while (r !== chlen && this.matches < this.maxMatches) { r = this._sbmh_feed(chunk) }
return r
}
SBMH.prototype._sbmh_feed = function (data) {
const len = data.length
const needle = this._needle
const needleLength = needle.length
const lastNeedleChar = needle[needleLength - 1]
// Positive: points to a position in `data`
// pos == 3 points to data[3]
// Negative: points to a position in the lookbehind buffer
// pos == -2 points to lookbehind[lookbehind_size - 2]
let pos = -this._lookbehind_size
let ch
if (pos < 0) {
// Lookbehind buffer is not empty. Perform Boyer-Moore-Horspool
// search with character lookup code that considers both the
// lookbehind buffer and the current round's haystack data.
//
// Loop until
// there is a match.
// or until
// we've moved past the position that requires the
// lookbehind buffer. In this case we switch to the
// optimized loop.
// or until
// the character to look at lies outside the haystack.
while (pos < 0 && pos <= len - needleLength) {
ch = this._sbmh_lookup_char(data, pos + needleLength - 1)
if (
ch === lastNeedleChar &&
this._sbmh_memcmp(data, pos, needleLength - 1)
) {
this._lookbehind_size = 0
++this.matches
this.emit('info', true)
return (this._bufpos = pos + needleLength)
}
pos += this._occ[ch]
}
// No match.
if (pos < 0) {
// There's too few data for Boyer-Moore-Horspool to run,
// so let's use a different algorithm to skip as much as
// we can.
// Forward pos until
// the trailing part of lookbehind + data
// looks like the beginning of the needle
// or until
// pos == 0
while (pos < 0 && !this._sbmh_memcmp(data, pos, len - pos)) { ++pos }
}
if (pos >= 0) {
// Discard lookbehind buffer.
this.emit('info', false, this._lookbehind, 0, this._lookbehind_size)
this._lookbehind_size = 0
} else {
// Cut off part of the lookbehind buffer that has
// been processed and append the entire haystack
// into it.
const bytesToCutOff = this._lookbehind_size + pos
if (bytesToCutOff > 0) {
// The cut off data is guaranteed not to contain the needle.
this.emit('info', false, this._lookbehind, 0, bytesToCutOff)
}
this._lookbehind.copy(this._lookbehind, 0, bytesToCutOff,
this._lookbehind_size - bytesToCutOff)
this._lookbehind_size -= bytesToCutOff
data.copy(this._lookbehind, this._lookbehind_size)
this._lookbehind_size += len
this._bufpos = len
return len
}
}
pos += (pos >= 0) * this._bufpos
// Lookbehind buffer is now empty. We only need to check if the
// needle is in the haystack.
if (data.indexOf(needle, pos) !== -1) {
pos = data.indexOf(needle, pos)
++this.matches
if (pos > 0) { this.emit('info', true, data, this._bufpos, pos) } else { this.emit('info', true) }
return (this._bufpos = pos + needleLength)
} else {
pos = len - needleLength
}
// There was no match. If there's trailing haystack data that we cannot
// match yet using the Boyer-Moore-Horspool algorithm (because the trailing
// data is less than the needle size) then match using a modified
// algorithm that starts matching from the beginning instead of the end.
// Whatever trailing data is left after running this algorithm is added to
// the lookbehind buffer.
while (
pos < len &&
(
data[pos] !== needle[0] ||
(
(Buffer.compare(
data.subarray(pos, pos + len - pos),
needle.subarray(0, len - pos)
) !== 0)
)
)
) {
++pos
}
if (pos < len) {
data.copy(this._lookbehind, 0, pos, pos + (len - pos))
this._lookbehind_size = len - pos
}
// Everything until pos is guaranteed not to contain needle data.
if (pos > 0) { this.emit('info', false, data, this._bufpos, pos < len ? pos : len) }
this._bufpos = len
return len
}
SBMH.prototype._sbmh_lookup_char = function (data, pos) {
return (pos < 0)
? this._lookbehind[this._lookbehind_size + pos]
: data[pos]
}
SBMH.prototype._sbmh_memcmp = function (data, pos, len) {
for (var i = 0; i < len; ++i) { // eslint-disable-line no-var
if (this._sbmh_lookup_char(data, pos + i) !== this._needle[i]) { return false }
}
return true
}
module.exports = SBMH
-196
View File
@@ -1,196 +0,0 @@
// Definitions by: Jacob Baskin <https://github.com/jacobbaskin>
// BendingBender <https://github.com/BendingBender>
// Igor Savin <https://github.com/kibertoad>
/// <reference types="node" />
import * as http from 'http';
import { Readable, Writable } from 'stream';
export { Dicer } from "../deps/dicer/lib/dicer";
export const Busboy: BusboyConstructor;
export default Busboy;
export interface BusboyConfig {
/**
* These are the HTTP headers of the incoming request, which are used by individual parsers.
*/
headers: BusboyHeaders;
/**
* `highWaterMark` to use for this Busboy instance.
* @default WritableStream default.
*/
highWaterMark?: number | undefined;
/**
* highWaterMark to use for file streams.
* @default ReadableStream default.
*/
fileHwm?: number | undefined;
/**
* Default character set to use when one isn't defined.
* @default 'utf8'
*/
defCharset?: string | undefined;
/**
* Detect if a Part is a file.
*
* By default a file is detected if contentType
* is application/octet-stream or fileName is not
* undefined.
*
* Modify this to handle e.g. Blobs.
*/
isPartAFile?: (fieldName: string | undefined, contentType: string | undefined, fileName: string | undefined) => boolean;
/**
* If paths in the multipart 'filename' field shall be preserved.
* @default false
*/
preservePath?: boolean | undefined;
/**
* Various limits on incoming data.
*/
limits?:
| {
/**
* Max field name size (in bytes)
* @default 100 bytes
*/
fieldNameSize?: number | undefined;
/**
* Max field value size (in bytes)
* @default 1MB
*/
fieldSize?: number | undefined;
/**
* Max number of non-file fields
* @default Infinity
*/
fields?: number | undefined;
/**
* For multipart forms, the max file size (in bytes)
* @default Infinity
*/
fileSize?: number | undefined;
/**
* For multipart forms, the max number of file fields
* @default Infinity
*/
files?: number | undefined;
/**
* For multipart forms, the max number of parts (fields + files)
* @default Infinity
*/
parts?: number | undefined;
/**
* For multipart forms, the max number of header key=>value pairs to parse
* @default 2000
*/
headerPairs?: number | undefined;
/**
* For multipart forms, the max size of a header part
* @default 81920
*/
headerSize?: number | undefined;
}
| undefined;
}
export type BusboyHeaders = { 'content-type': string } & http.IncomingHttpHeaders;
export interface BusboyFileStream extends
Readable {
truncated: boolean;
/**
* The number of bytes that have been read so far.
*/
bytesRead: number;
}
export interface Busboy extends Writable {
addListener<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
addListener(event: string | symbol, listener: (...args: any[]) => void): this;
on<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
on(event: string | symbol, listener: (...args: any[]) => void): this;
once<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
once(event: string | symbol, listener: (...args: any[]) => void): this;
removeListener<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
removeListener(event: string | symbol, listener: (...args: any[]) => void): this;
off<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
off(event: string | symbol, listener: (...args: any[]) => void): this;
prependListener<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
prependListener(event: string | symbol, listener: (...args: any[]) => void): this;
prependOnceListener<Event extends keyof BusboyEvents>(event: Event, listener: BusboyEvents[Event]): this;
prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this;
}
export interface BusboyEvents {
/**
* Emitted for each new file form field found.
*
* * Note: if you listen for this event, you should always handle the `stream` no matter if you care about the
* file contents or not (e.g. you can simply just do `stream.resume();` if you want to discard the contents),
* otherwise the 'finish' event will never fire on the Busboy instance. However, if you don't care about **any**
* incoming files, you can simply not listen for the 'file' event at all and any/all files will be automatically
* and safely discarded (these discarded files do still count towards `files` and `parts` limits).
* * If a configured file size limit was reached, `stream` will both have a boolean property `truncated`
* (best checked at the end of the stream) and emit a 'limit' event to notify you when this happens.
*
* @param listener.transferEncoding Contains the 'Content-Transfer-Encoding' value for the file stream.
* @param listener.mimeType Contains the 'Content-Type' value for the file stream.
*/
file: (
fieldname: string,
stream: BusboyFileStream,
filename: string,
transferEncoding: string,
mimeType: string,
) => void;
/**
* Emitted for each new non-file field found.
*/
field: (
fieldname: string,
value: string,
fieldnameTruncated: boolean,
valueTruncated: boolean,
transferEncoding: string,
mimeType: string,
) => void;
finish: () => void;
/**
* Emitted when specified `parts` limit has been reached. No more 'file' or 'field' events will be emitted.
*/
partsLimit: () => void;
/**
* Emitted when specified `files` limit has been reached. No more 'file' events will be emitted.
*/
filesLimit: () => void;
/**
* Emitted when specified `fields` limit has been reached. No more 'field' events will be emitted.
*/
fieldsLimit: () => void;
error: (error: unknown) => void;
}
export interface BusboyConstructor {
(options: BusboyConfig): Busboy;
new(options: BusboyConfig): Busboy;
}
-85
View File
@@ -1,85 +0,0 @@
'use strict'
const WritableStream = require('node:stream').Writable
const { inherits } = require('node:util')
const Dicer = require('../deps/dicer/lib/Dicer')
const MultipartParser = require('./types/multipart')
const UrlencodedParser = require('./types/urlencoded')
const parseParams = require('./utils/parseParams')
function Busboy (opts) {
if (!(this instanceof Busboy)) { return new Busboy(opts) }
if (typeof opts !== 'object') {
throw new TypeError('Busboy expected an options-Object.')
}
if (typeof opts.headers !== 'object') {
throw new TypeError('Busboy expected an options-Object with headers-attribute.')
}
if (typeof opts.headers['content-type'] !== 'string') {
throw new TypeError('Missing Content-Type-header.')
}
const {
headers,
...streamOptions
} = opts
this.opts = {
autoDestroy: false,
...streamOptions
}
WritableStream.call(this, this.opts)
this._done = false
this._parser = this.getParserByHeaders(headers)
this._finished = false
}
inherits(Busboy, WritableStream)
Busboy.prototype.emit = function (ev) {
if (ev === 'finish') {
if (!this._done) {
this._parser?.end()
return
} else if (this._finished) {
return
}
this._finished = true
}
WritableStream.prototype.emit.apply(this, arguments)
}
Busboy.prototype.getParserByHeaders = function (headers) {
const parsed = parseParams(headers['content-type'])
const cfg = {
defCharset: this.opts.defCharset,
fileHwm: this.opts.fileHwm,
headers,
highWaterMark: this.opts.highWaterMark,
isPartAFile: this.opts.isPartAFile,
limits: this.opts.limits,
parsedConType: parsed,
preservePath: this.opts.preservePath
}
if (MultipartParser.detect.test(parsed[0])) {
return new MultipartParser(this, cfg)
}
if (UrlencodedParser.detect.test(parsed[0])) {
return new UrlencodedParser(this, cfg)
}
throw new Error('Unsupported Content-Type.')
}
Busboy.prototype._write = function (chunk, encoding, cb) {
this._parser.write(chunk, cb)
}
module.exports = Busboy
module.exports.default = Busboy
module.exports.Busboy = Busboy
module.exports.Dicer = Dicer
-306
View File
@@ -1,306 +0,0 @@
'use strict'
// TODO:
// * support 1 nested multipart level
// (see second multipart example here:
// http://www.w3.org/TR/html401/interact/forms.html#didx-multipartform-data)
// * support limits.fieldNameSize
// -- this will require modifications to utils.parseParams
const { Readable } = require('node:stream')
const { inherits } = require('node:util')
const Dicer = require('../../deps/dicer/lib/Dicer')
const parseParams = require('../utils/parseParams')
const decodeText = require('../utils/decodeText')
const basename = require('../utils/basename')
const getLimit = require('../utils/getLimit')
const RE_BOUNDARY = /^boundary$/i
const RE_FIELD = /^form-data$/i
const RE_CHARSET = /^charset$/i
const RE_FILENAME = /^filename$/i
const RE_NAME = /^name$/i
Multipart.detect = /^multipart\/form-data/i
function Multipart (boy, cfg) {
let i
let len
const self = this
let boundary
const limits = cfg.limits
const isPartAFile = cfg.isPartAFile || ((fieldName, contentType, fileName) => (contentType === 'application/octet-stream' || fileName !== undefined))
const parsedConType = cfg.parsedConType || []
const defCharset = cfg.defCharset || 'utf8'
const preservePath = cfg.preservePath
const fileOpts = { highWaterMark: cfg.fileHwm }
for (i = 0, len = parsedConType.length; i < len; ++i) {
if (Array.isArray(parsedConType[i]) &&
RE_BOUNDARY.test(parsedConType[i][0])) {
boundary = parsedConType[i][1]
break
}
}
function checkFinished () {
if (nends === 0 && finished && !boy._done) {
finished = false
self.end()
}
}
if (typeof boundary !== 'string') { throw new Error('Multipart: Boundary not found') }
const fieldSizeLimit = getLimit(limits, 'fieldSize', 1 * 1024 * 1024)
const fileSizeLimit = getLimit(limits, 'fileSize', Infinity)
const filesLimit = getLimit(limits, 'files', Infinity)
const fieldsLimit = getLimit(limits, 'fields', Infinity)
const partsLimit = getLimit(limits, 'parts', Infinity)
const headerPairsLimit = getLimit(limits, 'headerPairs', 2000)
const headerSizeLimit = getLimit(limits, 'headerSize', 80 * 1024)
let nfiles = 0
let nfields = 0
let nends = 0
let curFile
let curField
let finished = false
this._needDrain = false
this._pause = false
this._cb = undefined
this._nparts = 0
this._boy = boy
const parserCfg = {
boundary,
maxHeaderPairs: headerPairsLimit,
maxHeaderSize: headerSizeLimit,
partHwm: fileOpts.highWaterMark,
highWaterMark: cfg.highWaterMark
}
this.parser = new Dicer(parserCfg)
this.parser.on('drain', function () {
self._needDrain = false
if (self._cb && !self._pause) {
const cb = self._cb
self._cb = undefined
cb()
}
}).on('part', function onPart (part) {
if (++self._nparts > partsLimit) {
self.parser.removeListener('part', onPart)
self.parser.on('part', skipPart)
boy.hitPartsLimit = true
boy.emit('partsLimit')
return skipPart(part)
}
// hack because streams2 _always_ doesn't emit 'end' until nextTick, so let
// us emit 'end' early since we know the part has ended if we are already
// seeing the next part
if (curField) {
const field = curField
field.emit('end')
field.removeAllListeners('end')
}
part.on('header', function (header) {
let contype
let fieldname
let parsed
let charset
let encoding
let filename
let nsize = 0
if (header['content-type']) {
parsed = parseParams(header['content-type'][0])
if (parsed[0]) {
contype = parsed[0].toLowerCase()
for (i = 0, len = parsed.length; i < len; ++i) {
if (RE_CHARSET.test(parsed[i][0])) {
charset = parsed[i][1].toLowerCase()
break
}
}
}
}
if (contype === undefined) { contype = 'text/plain' }
if (charset === undefined) { charset = defCharset }
if (header['content-disposition']) {
parsed = parseParams(header['content-disposition'][0])
if (!RE_FIELD.test(parsed[0])) { return skipPart(part) }
for (i = 0, len = parsed.length; i < len; ++i) {
if (RE_NAME.test(parsed[i][0])) {
fieldname = parsed[i][1]
} else if (RE_FILENAME.test(parsed[i][0])) {
filename = parsed[i][1]
if (!preservePath) { filename = basename(filename) }
}
}
} else { return skipPart(part) }
if (header['content-transfer-encoding']) { encoding = header['content-transfer-encoding'][0].toLowerCase() } else { encoding = '7bit' }
let onData,
onEnd
if (isPartAFile(fieldname, contype, filename)) {
// file/binary field
if (nfiles === filesLimit) {
if (!boy.hitFilesLimit) {
boy.hitFilesLimit = true
boy.emit('filesLimit')
}
return skipPart(part)
}
++nfiles
if (!boy._events.file) {
self.parser._ignore()
return
}
++nends
const file = new FileStream(fileOpts)
curFile = file
file.on('end', function () {
--nends
self._pause = false
checkFinished()
if (self._cb && !self._needDrain) {
const cb = self._cb
self._cb = undefined
cb()
}
})
file._read = function (n) {
if (!self._pause) { return }
self._pause = false
if (self._cb && !self._needDrain) {
const cb = self._cb
self._cb = undefined
cb()
}
}
boy.emit('file', fieldname, file, filename, encoding, contype)
onData = function (data) {
if ((nsize += data.length) > fileSizeLimit) {
const extralen = fileSizeLimit - nsize + data.length
if (extralen > 0) { file.push(data.slice(0, extralen)) }
file.truncated = true
file.bytesRead = fileSizeLimit
part.removeAllListeners('data')
file.emit('limit')
return
} else if (!file.push(data)) { self._pause = true }
file.bytesRead = nsize
}
onEnd = function () {
curFile = undefined
file.push(null)
}
} else {
// non-file field
if (nfields === fieldsLimit) {
if (!boy.hitFieldsLimit) {
boy.hitFieldsLimit = true
boy.emit('fieldsLimit')
}
return skipPart(part)
}
++nfields
++nends
let buffer = ''
let truncated = false
curField = part
onData = function (data) {
if ((nsize += data.length) > fieldSizeLimit) {
const extralen = (fieldSizeLimit - (nsize - data.length))
buffer += data.toString('binary', 0, extralen)
truncated = true
part.removeAllListeners('data')
} else { buffer += data.toString('binary') }
}
onEnd = function () {
curField = undefined
if (buffer.length) { buffer = decodeText(buffer, 'binary', charset) }
boy.emit('field', fieldname, buffer, false, truncated, encoding, contype)
--nends
checkFinished()
}
}
/* As of node@2efe4ab761666 (v0.10.29+/v0.11.14+), busboy had become
broken. Streams2/streams3 is a huge black box of confusion, but
somehow overriding the sync state seems to fix things again (and still
seems to work for previous node versions).
*/
part._readableState.sync = false
part.on('data', onData)
part.on('end', onEnd)
}).on('error', function (err) {
if (curFile) { curFile.emit('error', err) }
})
}).on('error', function (err) {
boy.emit('error', err)
}).on('finish', function () {
finished = true
checkFinished()
})
}
Multipart.prototype.write = function (chunk, cb) {
const r = this.parser.write(chunk)
if (r && !this._pause) {
cb()
} else {
this._needDrain = !r
this._cb = cb
}
}
Multipart.prototype.end = function () {
const self = this
if (self.parser.writable) {
self.parser.end()
} else if (!self._boy._done) {
process.nextTick(function () {
self._boy._done = true
self._boy.emit('finish')
})
}
}
function skipPart (part) {
part.resume()
}
function FileStream (opts) {
Readable.call(this, opts)
this.bytesRead = 0
this.truncated = false
}
inherits(FileStream, Readable)
FileStream.prototype._read = function (n) {}
module.exports = Multipart
-190
View File
@@ -1,190 +0,0 @@
'use strict'
const Decoder = require('../utils/Decoder')
const decodeText = require('../utils/decodeText')
const getLimit = require('../utils/getLimit')
const RE_CHARSET = /^charset$/i
UrlEncoded.detect = /^application\/x-www-form-urlencoded/i
function UrlEncoded (boy, cfg) {
const limits = cfg.limits
const parsedConType = cfg.parsedConType
this.boy = boy
this.fieldSizeLimit = getLimit(limits, 'fieldSize', 1 * 1024 * 1024)
this.fieldNameSizeLimit = getLimit(limits, 'fieldNameSize', 100)
this.fieldsLimit = getLimit(limits, 'fields', Infinity)
let charset
for (var i = 0, len = parsedConType.length; i < len; ++i) { // eslint-disable-line no-var
if (Array.isArray(parsedConType[i]) &&
RE_CHARSET.test(parsedConType[i][0])) {
charset = parsedConType[i][1].toLowerCase()
break
}
}
if (charset === undefined) { charset = cfg.defCharset || 'utf8' }
this.decoder = new Decoder()
this.charset = charset
this._fields = 0
this._state = 'key'
this._checkingBytes = true
this._bytesKey = 0
this._bytesVal = 0
this._key = ''
this._val = ''
this._keyTrunc = false
this._valTrunc = false
this._hitLimit = false
}
UrlEncoded.prototype.write = function (data, cb) {
if (this._fields === this.fieldsLimit) {
if (!this.boy.hitFieldsLimit) {
this.boy.hitFieldsLimit = true
this.boy.emit('fieldsLimit')
}
return cb()
}
let idxeq; let idxamp; let i; let p = 0; const len = data.length
while (p < len) {
if (this._state === 'key') {
idxeq = idxamp = undefined
for (i = p; i < len; ++i) {
if (!this._checkingBytes) { ++p }
if (data[i] === 0x3D/* = */) {
idxeq = i
break
} else if (data[i] === 0x26/* & */) {
idxamp = i
break
}
if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) {
this._hitLimit = true
break
} else if (this._checkingBytes) { ++this._bytesKey }
}
if (idxeq !== undefined) {
// key with assignment
if (idxeq > p) { this._key += this.decoder.write(data.toString('binary', p, idxeq)) }
this._state = 'val'
this._hitLimit = false
this._checkingBytes = true
this._val = ''
this._bytesVal = 0
this._valTrunc = false
this.decoder.reset()
p = idxeq + 1
} else if (idxamp !== undefined) {
// key with no assignment
++this._fields
let key; const keyTrunc = this._keyTrunc
if (idxamp > p) { key = (this._key += this.decoder.write(data.toString('binary', p, idxamp))) } else { key = this._key }
this._hitLimit = false
this._checkingBytes = true
this._key = ''
this._bytesKey = 0
this._keyTrunc = false
this.decoder.reset()
if (key.length) {
this.boy.emit('field', decodeText(key, 'binary', this.charset),
'',
keyTrunc,
false)
}
p = idxamp + 1
if (this._fields === this.fieldsLimit) { return cb() }
} else if (this._hitLimit) {
// we may not have hit the actual limit if there are encoded bytes...
if (i > p) { this._key += this.decoder.write(data.toString('binary', p, i)) }
p = i
if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) {
// yep, we actually did hit the limit
this._checkingBytes = false
this._keyTrunc = true
}
} else {
if (p < len) { this._key += this.decoder.write(data.toString('binary', p)) }
p = len
}
} else {
idxamp = undefined
for (i = p; i < len; ++i) {
if (!this._checkingBytes) { ++p }
if (data[i] === 0x26/* & */) {
idxamp = i
break
}
if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) {
this._hitLimit = true
break
} else if (this._checkingBytes) { ++this._bytesVal }
}
if (idxamp !== undefined) {
++this._fields
if (idxamp > p) { this._val += this.decoder.write(data.toString('binary', p, idxamp)) }
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
decodeText(this._val, 'binary', this.charset),
this._keyTrunc,
this._valTrunc)
this._state = 'key'
this._hitLimit = false
this._checkingBytes = true
this._key = ''
this._bytesKey = 0
this._keyTrunc = false
this.decoder.reset()
p = idxamp + 1
if (this._fields === this.fieldsLimit) { return cb() }
} else if (this._hitLimit) {
// we may not have hit the actual limit if there are encoded bytes...
if (i > p) { this._val += this.decoder.write(data.toString('binary', p, i)) }
p = i
if ((this._val === '' && this.fieldSizeLimit === 0) ||
(this._bytesVal = this._val.length) === this.fieldSizeLimit) {
// yep, we actually did hit the limit
this._checkingBytes = false
this._valTrunc = true
}
} else {
if (p < len) { this._val += this.decoder.write(data.toString('binary', p)) }
p = len
}
}
}
cb()
}
UrlEncoded.prototype.end = function () {
if (this.boy._done) { return }
if (this._state === 'key' && this._key.length > 0) {
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
'',
this._keyTrunc,
false)
} else if (this._state === 'val') {
this.boy.emit('field', decodeText(this._key, 'binary', this.charset),
decodeText(this._val, 'binary', this.charset),
this._keyTrunc,
this._valTrunc)
}
this.boy._done = true
this.boy.emit('finish')
}
module.exports = UrlEncoded

Some files were not shown because too many files have changed in this diff Show More