Compare commits

...

140 Commits

Author SHA1 Message Date
147b11b22d bump: version 1.5.9 2026-02-27 14:30:03 -05:00
454d13c608 Update XML and M3U file paths to version 2.0.0 2026-02-09 09:22:14 -05:00
renovate[bot]
6086dbbad2 chore(deps): lock file maintenance (#143)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-09 06:02:12 +00:00
renovate[bot]
b9607dddce chore(deps): lock file maintenance (#140)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-02 04:26:06 +00:00
renovate[bot]
1a7aeb4450 chore(deps): lock file maintenance (#137)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-26 05:43:30 +00:00
renovate[bot]
d973af6a8d chore(deps): lock file maintenance (#126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-19 20:51:12 +00:00
2dae279f93 ci: switch runner in workflow 2026-01-08 12:34:23 -07:00
09d17717ab ci: update gitea workflow 2026-01-08 12:23:24 -07:00
bf4454f635 ci: update workflow network 2026-01-08 12:05:21 -07:00
9e531d823f ci: update release workflow 2026-01-08 11:57:25 -07:00
d17aa23e98 Merge pull request #133 from TheBinaryNinja/m3u-format
[FEATURE]: Add m3u playlist automated generation and validator
2026-01-07 23:44:37 -05:00
63f7c1d665 Change extM3U URL to XML EPG path
Updated extM3U URL to point to XML EPG location.
2026-01-07 23:20:47 -05:00
c5c2f741f0 ci: remove old index.js 2025-10-08 07:44:18 -07:00
ec24c51eea chore(lint): run linter 2025-10-08 07:41:19 -07:00
fa2c4073e3 ci: update gitea workflow 2025-10-08 06:54:56 -07:00
255d093269 ci: update gitea workflow 2025-10-08 06:51:24 -07:00
73a264b1c2 build: bump version to 1.5.8 2025-10-08 06:27:17 -07:00
iFlip721
c112230e05 update EPG endpoint with new pull location 2025-10-06 19:53:44 -04:00
renovate[bot]
02dd911e93 chore(deps): lock file maintenance (#123)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-06 05:45:30 +00:00
9c3ee3d146 refactor: update eslint corrections 2025-10-05 00:57:02 -07:00
4c8d5d03d9 chore(deps): update dependency uuid to v13 2025-10-05 00:07:47 -07:00
c729594864 build: populate channel urls from gitea repo 2025-10-05 00:06:12 -07:00
713626810b fix: update binaryninja gitea url 2025-10-04 23:37:05 -07:00
6e5c261065 ci: update docker flow 2025-10-01 03:29:38 -07:00
2f1027e068 ci: update docker build flow 2025-10-01 03:27:02 -07:00
739f547731 ci: update docker parameters 2025-10-01 03:22:19 -07:00
3f7ecdb84e ci: update docker buildx flags 2025-10-01 03:15:55 -07:00
e037764c3f ci: edit buildx installation parameters 2025-10-01 03:13:48 -07:00
c8aa866dfd ci: update docker build workflow for invalid / insecure ssl certs 2025-10-01 02:58:40 -07:00
84b1199878 ci: update docker restart command 2025-10-01 02:54:18 -07:00
04150d5320 ci: update bootstrap for docker buildx 2025-10-01 02:50:29 -07:00
11ccf2909f ci: update gitea workflow 2025-10-01 02:41:52 -07:00
631942ca75 docs(readme): add HDHR_PORT to env variables 2025-10-01 02:00:55 -07:00
4ee603d7a2 fix(hdhr): animated uptime now counting 2025-10-01 00:48:17 -07:00
7cfe22b72e feat: add HDHomeRun lineup.json to api 2025-10-01 00:47:55 -07:00
e6701cda95 feat: add silence health check to HDHomeRun server 2025-10-01 00:40:35 -07:00
865a2fd645 feat: add Hdhr.SlotsConnected and Hdhr:SlotsMax
add slot count to template and class
2025-10-01 00:34:07 -07:00
05f362153f docs(traefik): update traefik configs
add HDHomeRun
2025-10-01 00:11:04 -07:00
997eb72378 build(package): bump version v1.5.6 2025-09-30 23:54:39 -07:00
69805151c8 fix(hdhr): assign new var to tuner instance 2025-09-30 23:52:15 -07:00
47ec5267ec feat: add HDHomeRun website page when accessing port 6077 2025-09-30 23:50:27 -07:00
3a87b51f41 refactor(hdhr): add customizable HDHomeRun port 2025-09-30 23:07:43 -07:00
ffc8cfe68e build(dockerfile): add new env var HDHR_PORT 2025-09-30 23:04:29 -07:00
7f5fffa5e6 docs(license): update 2025-09-30 23:02:51 -07:00
b16f4a9fb3 refactor: remove docker.sock from examples/docker-compose.yml - fixes #105 2025-09-30 23:01:29 -07:00
ebf0b84a05 refactor: move classes migrated to dedicated class files 2025-09-30 22:58:37 -07:00
b724930c6a feat: add HDHomeRun core server functionality 2025-09-30 22:58:06 -07:00
603e444d35 refactor: migrate classes to dedicated class files; add new imports 2025-09-30 22:56:30 -07:00
f274b807f2 refactor: change the way domain checks validate a domain 2025-09-30 22:55:14 -07:00
d0c8920b98 fix(m3u): source 3 offline due to dns change 2025-09-30 22:52:56 -07:00
4c0d49508f feat: add git.binaryninja.net health check; stop overwriting m3u/epg when down
stops the app from wiping the m3u and xml file if the bit.binaryninja.net repo /website is down
2025-09-30 22:52:21 -07:00
2a09bc1ea3 feat: add new classes structure 2025-09-30 22:49:37 -07:00
259d27a2ce build: publish utils class 2025-09-30 22:47:57 -07:00
renovate[bot]
8aefbb39e0 chore(deps): lock file maintenance (#120)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-29 04:33:48 +00:00
renovate[bot]
e417b9f5d8 chore(deps): lock file maintenance (#118)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-22 05:30:37 +00:00
renovate[bot]
9458587d59 chore(deps): lock file maintenance (#114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-15 23:27:53 +00:00
renovate[bot]
468c8c10fc chore(deps): lock file maintenance (#109)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 05:07:05 +00:00
renovate[bot]
6d90a88b60 chore(deps): lock file maintenance (#104)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 13:33:04 +00:00
renovate[bot]
7231199f9e chore(deps): lock file maintenance (#101)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-18 06:40:11 +00:00
renovate[bot]
41c0c9f685 chore(deps): lock file maintenance (#100)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-14 10:51:11 +00:00
renovate[bot]
79c5c648c9 chore(deps): lock file maintenance (#94)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-28 05:56:54 +00:00
renovate[bot]
0ba2e23171 chore(deps): lock file maintenance (#91)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-21 06:54:22 +00:00
renovate[bot]
b0f3869621 chore(deps): lock file maintenance (#89)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-14 06:08:51 +00:00
renovate[bot]
b709d53e40 chore(deps): lock file maintenance (#87)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-07-07 07:34:57 +00:00
renovate[bot]
b198168d75 chore(deps): lock file maintenance (#84)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-30 05:35:34 +00:00
bd41ab603d build: update docker-compose.yml example
added new quiet param to healthcheck
2025-06-23 03:25:37 -07:00
0059431fbb ci: update release workflows 2025-06-23 03:19:17 -07:00
863addce39 ci: update release workflow; automatic versioning 2025-06-23 03:08:03 -07:00
07b7272eb1 build: bump version v1.5.5 2025-06-23 02:52:39 -07:00
c59de1fcf9 feat: notifications fade out after first health check 2025-06-23 02:51:35 -07:00
2d24d8e379 feat: update webui interface 2025-06-23 02:38:02 -07:00
60fd32e4d5 feat: add new query param silent 2025-06-22 21:24:08 -07:00
f32504e76b fix: tooltip positioning in webui now shows in the correct location 2025-06-22 21:23:03 -07:00
8eed126fa4 build: remove packages playwright, user-agents 2025-06-22 21:04:20 -07:00
9242cbccc4 Merge pull request #79 from TheBinaryNinja/renovate/major-eslint-stylistic-monorepo 2025-06-22 21:03:20 -07:00
renovate[bot]
a7d209b370 chore(deps): lock file maintenance (#80)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-23 04:01:04 +00:00
29c1b6286f ci: update workflow release 2025-06-22 20:58:46 -07:00
renovate[bot]
1ae4ab46d4 chore(deps): update dependency @stylistic/eslint-plugin to v5 2025-06-23 01:52:17 +00:00
cd33470b12 docs(readme): update 2025-06-21 15:55:50 -07:00
083feeef90 docs(readme): update 2025-06-21 15:54:23 -07:00
42f6267539 docs(readme): update 2025-06-21 15:51:45 -07:00
renovate[bot]
5f669092c2 chore(deps): lock file maintenance (#75)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-17 02:11:47 +00:00
9a36aad9cb docs(readme): update 2025-06-14 01:42:05 -07:00
898983f724 Update README.md 2025-06-13 18:47:56 -07:00
c35a726e93 ci: update workflows 2025-06-13 15:56:55 -07:00
9cda4061d5 ci: update roadmap issue template 2025-06-11 21:29:52 -07:00
1462dffb3e style(lint): correct eslint errors 2025-06-11 21:25:02 -07:00
d314242b3b fix(lint): invalid value always on rule import/no-default-export 2025-06-11 21:02:05 -07:00
e6daa0bb20 feat: add os detection for windows and linux 2025-06-11 20:40:18 -07:00
3b357881ae docs(readme): update 2025-06-11 19:41:28 -07:00
20efc2b0e2 build: set default alpine version to 3.22 2025-06-11 19:35:58 -07:00
6a9ca9993a build(deps): add package dependencies 2025-06-11 18:33:36 -07:00
0c0c3f4671 chore: add new variable to ejs template 2025-06-11 18:28:49 -07:00
5a21488e6b feat: add operating system to footer 2025-06-11 18:23:41 -07:00
e5c7c3df7b build(deps-dev): bump package eslint-plugin-chai-friendly from 1.0.1 to 1.1.0 2025-06-11 18:11:05 -07:00
78015e1364 build(deps-dev): bump package eslint-plugin-n from 17.18.0 to 17.19.0 2025-06-11 18:09:59 -07:00
ffcb30cdbd ci: update distro variable order 2025-06-11 18:04:34 -07:00
4ff15c7613 ci: update deployment workflows 2025-06-11 18:03:02 -07:00
db78571c25 ci: bump alpine version for workflow deploy-docker-github 2025-06-11 17:33:44 -07:00
590aa9a1ff chore: update distro detection 2025-06-11 17:33:18 -07:00
ccab0685d0 feat: add distro information to startup console 2025-06-11 17:27:52 -07:00
34cc9789eb build: bump app to v1.5.4 2025-06-11 17:04:15 -07:00
82ac23c63c fix: dynamic uptime on footer causing spam in console 2025-06-11 17:02:55 -07:00
7065aeba69 ci: update deployment workflow 2025-06-11 16:30:44 -07:00
bf23c998a0 ci: update deployment workflow 2025-06-11 16:27:48 -07:00
35e0f97b0a ci: update workflow labels-create 2025-06-11 16:07:21 -07:00
3fe80a12e0 ci: update workflow issues-new 2025-06-11 16:06:43 -07:00
03f53d9f11 ci: add workflow ping-developer 2025-06-11 16:04:40 -07:00
c58f6dd883 ci: update workflow history-clean 2025-06-11 16:04:07 -07:00
9a2b76b887 ci: update workflow documentation 2025-06-11 16:03:15 -07:00
3d380851ab ci: update workflow deploy-clean 2025-06-11 16:00:19 -07:00
5b2b76b772 ci: add workflow gpg-tests 2025-06-11 15:59:48 -07:00
80b22d23b7 ci: add workflow cache-clean 2025-06-11 15:58:58 -07:00
c3d2b9efc1 ci: update workflow issues-scan 2025-06-11 15:57:23 -07:00
25c4569639 ci: update workflow issues-stale 2025-06-11 15:51:13 -07:00
78a512b8f6 ci: update workflow labels-clean 2025-06-11 15:49:52 -07:00
3d5d11523e ci: update labels-create workflow 2025-06-11 15:48:49 -07:00
d2ffa5e381 build: stop tracking tvapp2/node_modules 2025-06-10 18:05:05 -07:00
b4eb11818f build(package.json): update semver allowance so that users can now update packages with a container restart 2025-06-10 17:28:42 -07:00
a6032241e1 chore(lint): disable rule import/no-default-export 2025-06-10 14:34:54 -07:00
b1e2c48075 build(package.json): update files property 2025-06-10 14:34:24 -07:00
a77087a081 docs(readme): fix typo in gitea url
`binarynina` changed to `binaryninja`
2025-06-10 09:59:23 -07:00
05319b3860 docs(readme): update 2025-06-10 09:55:08 -07:00
fb106a1499 build(deps-dev): replace package @stylistic/eslint-plugin-js with @stylistic/eslint-plugin 2025-06-10 09:52:16 -07:00
9eaa896b2d ci: revert action script softprops/action-gh-release from v2.3.0 to v2.2.2
Latest version 2.3.0 contains bugs; see https://github.com/softprops/action-gh-release/issues/627
2025-06-10 09:17:20 -07:00
ed7abff25a ci: add .sig generation to release workflow 2025-06-10 09:14:47 -07:00
4a8f35c0eb refactor(lint): There must be a space after this paren @stylistic/js/space-in-parens 2025-06-10 09:06:10 -07:00
1debab8452 refactor: add uptimeLong to api 2025-06-10 08:49:49 -07:00
53cfd4789e fix: health timer resetting due to incompatible code 2025-06-10 08:28:32 -07:00
d9a89e143a feat: add uptime, startup time logic 2025-06-10 08:12:31 -07:00
55e998bb46 feat: add uptime, startup time in footer 2025-06-10 07:58:11 -07:00
bd40d20911 build: bump version 1.5.3 2025-06-10 07:55:14 -07:00
efc5dac8f4 build(deps): add package nconf
package nconf will be utilized to store HDHomeRun config settings
2025-06-02 03:41:06 -07:00
c33a35003b ci: update docker annotations to add chomp 2025-06-01 16:56:44 -07:00
a10c1bcff9 ci: add org.opencontainers.image.description 2025-06-01 16:41:53 -07:00
2025e04b61 ci: update env var IMAGE_ALPINE_VERSION 2025-06-01 16:38:33 -07:00
77e2b5e7d6 ci: add network settings, add annotations 2025-06-01 16:31:59 -07:00
9b2b7682e3 ci: update build scripts, add default ARG values 2025-06-01 15:37:14 -07:00
433abb0fec feat: refresh health timer automatically during hover 2025-06-01 14:12:49 -07:00
012cd0cc44 feat: add git hash to footer 2025-06-01 12:38:10 -07:00
447 changed files with 8352 additions and 139673 deletions

View File

@@ -41,6 +41,7 @@ body:
- Distribution
- Documentation
- M3U / EPG Functionality
- Refactor (Code)
- Repository
- S6-Overlay
default: 0

302
.github/workflows/cache-clean.yml vendored Normal file
View File

@@ -0,0 +1,302 @@
# #
# @type github workflow
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage cleans up the cache for a repository.
#
#
# You can view your current cached items by going to Github and going to the page:
# https://github.com/USERNAME/REPO/actions/caches
#
# Caches have branch scope restrictions in place, which means some caches have limited usage options. For more information
# on cache scope restrictions, see Restrictions for accessing a cache, earlier in this article. If caches limited to a specific
# branch are using a lot of storage quota, it may cause caches from the default branch to be created and deleted at a high frequency.
#
# For example, a repository could have many new pull requests opened, each with their own caches that are restricted to that branch.
# These caches could take up the majority of the cache storage for that repository. Once a repository has reached its maximum cache
# storage, the cache eviction policy will create space by deleting the caches in order of last access date, from oldest to most
# recent. In order to prevent cache thrashing when this happens, you can set up workflows to delete caches on a faster cadence than
# the cache eviction policy will. You can use the GitHub CLI to delete caches for specific branches.
#
# @reference https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#deleting-cache-entries
#
#
# @secrets secrets.SELF_TOKEN self github personal access token (fine-grained)
# secrets.SELF_TOKEN_CL self github personal access token (classic)
# secrets.NPM_TOKEN self npmjs access token
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_UPDATES discord webhook to report activity notifications from github to discord
#
#
# @local these workflows can be tested locally through the use of `act`
# https://github.com/nektos/act
# Extract act to folder
# Add system env var with path to act.exe
# Run the commands:
# git pull https://github.com/username/repo
# act -W .github/workflows/cache-clean.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/cache-clean.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
# #
name: '🧹 Cache Clean'
run-name: '🧹 Cache Clean'
# #
# triggers
# #
on:
# #
# Trigger Workflow Dispatch
# #
workflow_dispatch:
inputs:
# #
# Cache Num Per Page
#
# this is the number of cached items to fetch per page.
# #
NUM_PER_PAGE:
description: '📦 Number Per Page'
required: true
default: '100'
type: string
# #
# environment variables
# #
env:
NUM_PER_PAGE: ${{ github.event.inputs.NUM_PER_PAGE || '100' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# jobs
# #
jobs:
cleanup:
name: >-
🧹 Cache Clean
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
permissions: write-all
steps:
# #
# Cache Clean Checkout
# #
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Cache Clean Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Cache Clean Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Cache Clean Start
#
# You can view your current cached items by going to Github and going to the page:
# https://github.com/USERNAME/REPO/actions/caches
#
# Caches have branch scope restrictions in place, which means some caches have limited usage options. For more information
# on cache scope restrictions, see Restrictions for accessing a cache, earlier in this article. If caches limited to a specific
# branch are using a lot of storage quota, it may cause caches from the default branch to be created and deleted at a high frequency.
#
# For example, a repository could have many new pull requests opened, each with their own caches that are restricted to that branch.
# These caches could take up the majority of the cache storage for that repository. Once a repository has reached its maximum cache
# storage, the cache eviction policy will create space by deleting the caches in order of last access date, from oldest to most
# recent. In order to prevent cache thrashing when this happens, you can set up workflows to delete caches on a faster cadence than
# the cache eviction policy will. You can use the GitHub CLI to delete caches for specific branches.
#
# @reference https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#deleting-cache-entries
# #
- name: >-
🪣 Cache Clean
uses: actions/github-script@v7
with:
script: |
try
{
const cacheConf =
{
owner: context.repo.owner,
repo: context.repo.repo,
per_page: ${{ env.NUM_PER_PAGE }},
};
console.log( `✅ Starting to clean cache; showing ${{ env.NUM_PER_PAGE }} cached items per page` )
const caches = await github.rest.actions.getActionsCacheList( cacheConf )
while( caches.data.actions_caches.length )
{
for ( const cache of caches.data.actions_caches )
{
console.log( `⚠️ Clearing cache id: ${cache.key} (${cache.id})` )
const res = await github.rest.actions.deleteActionsCacheById(
{
owner: context.repo.owner,
repo: context.repo.repo,
cache_id: cache.id,
});
console.log( `✔️ Cache item ${cache.key} (${cache.id}) cleared at ${new Date().toISOString()}` )
}
caches = await github.rest.actions.getActionsCacheList(congif);
console.log( `Getting another ${caches.data.actions_caches.length} caches from github ...` );
}
console.log( `✔️ Finished cleaning the cache` );
}
catch( e )
{
console.log( `❌ The workflow has terminated with an error:`, e )
return;
}
# #
# Cache Clean Get Weekly Commits
# #
- name: >-
🕛 Get Weekly Commit List
run: |
echo 'WEEKLY_COMMITS<<EOF' >> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV

View File

@@ -12,11 +12,16 @@
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
@@ -75,6 +80,54 @@ on:
default: '1000'
type: string
# #
# Discord Bot Name
#
# The discord bot name
# #
DISCORD_BOT_NAME:
description: '🤖 Bot Name'
required: true
default: 'Europa'
type: string
# #
# Discord Bot Avatar
#
# The discord bot avatar to show; let's use some weird picture
# #
DISCORD_BOT_AVATAR:
description: '🤖 Avatar URL'
required: true
default: 'https://i.imgur.com/UqwMom1.jpeg'
type: string
# #
# Discord Bot Author Icon URL
#
# A small picture shown to the top-right of each post
# #
DISCORD_BOT_EMBED_AUTHOR_ICON:
description: '🤖 Embed Author Icon'
required: true
default: 'https://avatars.githubusercontent.com/u/200161462'
type: string
# #
# Discord Bot Thumbnail URL
#
# A small picture shown to the top-right of each post
# #
DISCORD_BOT_EMBED_THUMBNAIL:
description: '🤖 Embed Thumbnail URL'
required: true
default: 'https://avatars.githubusercontent.com/u/200161462'
type: string
# #
# environment variables
# #
@@ -82,6 +135,10 @@ on:
env:
DEPLOYMENT_ENV: ${{ github.event.inputs.DEPLOYMENT_ENV || 'orion' }}
DEPLOYMENT_DELAY: ${{ github.event.inputs.DEPLOYMENT_DELAY || '1000' }}
DISCORD_BOT_NAME: ${{ github.event.inputs.DISCORD_BOT_NAME || 'Europa' }}
DISCORD_BOT_AVATAR: ${{ github.event.inputs.DISCORD_BOT_AVATAR || 'https://i.imgur.com/UqwMom1.jpeg' }}
DISCORD_BOT_EMBED_AUTHOR_ICON: ${{ github.event.inputs.DISCORD_BOT_EMBED_AUTHOR_ICON || 'https://avatars.githubusercontent.com/u/200161462' }}
DISCORD_BOT_EMBED_THUMBNAIL: ${{ github.event.inputs.DISCORD_BOT_EMBED_THUMBNAIL || 'https://avatars.githubusercontent.com/u/200161462' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
@@ -95,45 +152,137 @@ jobs:
cleanup:
name: >-
🧹 Deployments Clean
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
permissions: write-all
steps:
# #
# Cleanup Set Env Variables
# Deployments Cleanup Checkout
# #
- name: >-
🕛 Get Timestamp
id: task_cleanup_set_timestamp
run: |
echo "YEAR=$(date +'%Y')" >> $GITHUB_ENV
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
# #
# Release Github Checkout Arm64
# #
- name: >-
✅ Checkout
id: task_cleanup_gh_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Cleanup Start
# Deployments Cleanup Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Deployments Cleanup Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Deployments Cleanup Start
# #
- name: >-
⚙️ Deployments Clean
id: task_cleanup_start
uses: Aetherinox/delete-deploy-env-action@v3
with:
token: ${{ secrets.SELF_TOKEN_CL }}
@@ -142,34 +291,31 @@ jobs:
delay: "${{ env.DEPLOYMENT_DELAY }}"
# #
# Cleanup Get Weekly Commits
# Deployments Cleanup Get Weekly Commits
# #
- name: >-
🕛 Get Weekly Commit List
id: task_cleanup_set_weekly_commit_list
run: |
echo 'WEEKLY_COMMITS<<EOF' >> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
# #
# Cleanup Notify Github Success
# Deployments Cleanup Notify Github Success
# #
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_cleanup_notify_discord_success
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## 📦 Deployment Cleanup ${{ job.status == 'success' && '✅' || '❌' }}
@@ -188,24 +334,23 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}
# #
# Cleanup Notify Github Failure
# Deployments Cleanup Notify Github Failure
# #
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_cleanup_notify_discord_failure
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## 📦 Deployment Cleanup ${{ job.status == 'success' && '✅' || '❌' }}
@@ -224,5 +369,5 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}

View File

@@ -63,15 +63,18 @@ on:
type: string
# #
# Image Version
# Registry Name
#
# used to create new release tag, and add version to docker image name
# options:
# - github
# - dockerhub
# - gitea
# #
IMAGE_VERSION:
description: '🏷️ Image Version'
IMAGE_REGISTRY:
description: '📘 Registry Name'
required: true
default: '1.0.0'
default: 'dockerhub'
type: string
# #
@@ -100,6 +103,18 @@ on:
default: 'thebinaryninja'
type: string
# #
# Alpine Version
#
# specifies the alpine base docker image version
# #
IMAGE_ALPINE_VERSION:
description: '📀 Alpine Version'
required: true
default: '3.22'
type: string
# #
# true no changes to the repo will be made
# false workflow will behave normally, and push any changes detected to the files
@@ -136,9 +151,14 @@ on:
env:
IMAGE_NAME: ${{ github.event.inputs.IMAGE_NAME || 'tvapp2' }}
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_REGISTRY || 'dockerhub' }}
IMAGE_DOCKERHUB_AUTHOR: ${{ github.event.inputs.IMAGE_DOCKERHUB_AUTHOR || 'thebinaryninja' }}
IMAGE_DOCKERHUB_USERNAME: ${{ github.event.inputs.IMAGE_DOCKERHUB_USERNAME || 'thebinaryninja' }}
IMAGE_ALPINE_VERSION: ${{ github.event.inputs.IMAGE_ALPINE_VERSION || '3.22' }}
DISCORD_BOT_NAME: 'Europa'
DISCORD_BOT_AVATAR: 'https://i.imgur.com/UqwMom1.jpeg'
DISCORD_BOT_EMBED_AUTHOR_ICON: 'https://avatars.githubusercontent.com/u/200161462'
DISCORD_BOT_EMBED_THUMBNAIL: 'https://avatars.githubusercontent.com/u/200161462'
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
@@ -162,9 +182,11 @@ jobs:
job-docker-release-tags-create:
name: >-
📦 Release Create Tag
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 4
outputs:
package_version: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}
permissions:
contents: write
packages: write
@@ -172,25 +194,144 @@ jobs:
id-token: write
steps:
# #
# Release Tags Start
# #
- name: '🏳️ Start'
id: task_release_tags_start
run: |
echo "Creating Tag"
# #
# Release Tags Checkout
# #
- name: ' Checkout'
id: task_release_tags_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Tags Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Release Tags Set Package.json Version
# #
- name: '👁️‍🗨️ Package Version Set'
id: task_initialize_package_getversion
working-directory: ./tvapp2
run: |
VER=$(cat package.json | jq -r '.version')
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_OUTPUT
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_ENV
# #
# Initialize Get Package.json Version
# #
- name: '👁️‍🗨️ Package Version Get'
run: |
echo "VERSION: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}"
# #
# Release Tags Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (env.SHA1) .. ${{ env.SHA1 }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Release Tags Fix Permissions
# #
@@ -220,9 +361,9 @@ jobs:
id: task_release_tags_create
if: ( github.event_name != 'workflow_dispatch' && inputs.DRY_RUN == false )
with:
tag: "${{ env.IMAGE_VERSION }}"
tag: "${{ env.PACKAGE_VERSION }}"
tag_exists_error: false
message: '${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}'
message: '${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}'
gpg_private_key: ${{ secrets.ADMINSERV_GPG_KEY_ASC }}
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
@@ -233,61 +374,152 @@ jobs:
job-docker-release-dockerhub:
name: >-
📦 Release Dockerhub
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 10
needs: [ job-docker-release-tags-create ]
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create ]
env:
PACKAGE_VERSION: ${{ needs.job-docker-release-tags-create.outputs.package_version }}
steps:
# #
# Release Dockerhub Start Amd64
# Release Dockerhub Checkout
# #
- name: '🏳 Start'
id: task_release_dh_start
run: |
echo "Starting Dockerhub docker release"
# #
# Release Dockerhub Checkout Amd64
# #
- name: '✅ Checkout'
id: task_release_gh_checkout
- name: ' Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Dockerhub Get Timestamp
# Release Dockerhub Job Information
# #
- name: '🕛 Get Timestamp'
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Release Dockerhub Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (env.SHA1) .. ${{ env.SHA1 }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Release Dockerhub Set Vars
# #
- name: '🕛 Set Vars'
id: task_release_set_timestamp
run: |
echo "IMAGE_VERSION_1DIGIT=`echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1`" >> ${GITHUB_ENV} # 1
echo "IMAGE_VERSION_2DIGIT=`echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-2`" >> ${GITHUB_ENV} # 1.0
echo "REGISTRY_REPO_ORG_AUTHOR_LC=`echo ${{ env.IMAGE_DOCKERHUB_AUTHOR }}/${{ env.IMAGE_NAME }} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} # thebinaryninja/tvapp2
echo "REGISTRY_REPO_AUTHOR_LC=`echo ${{ env.IMAGE_DOCKERHUB_AUTHOR }} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} # thebinaryninja
echo "DOCKER_SHA=${GITHUB_SHA}" >> $GITHUB_ENV # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV # 02-25-2025 12:49:48
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV # 02-25-2025
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV # 02-25-2025 12:49
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV # 20250225
echo "NOW_DOCKER_TS=$(date -u +'%FT%T.%3NZ')" >> $GITHUB_ENV # 2025-02-25T12:50:11.569Z
echo "GITHUB_SHA1=`git rev-parse HEAD`" >> ${GITHUB_ENV}
# #
# Release Dockerhub Install Dependencies
# #
- name: '📦 Install Dependencies'
id: task_release_dh_dependencies
run:
sudo apt-get install -qq dos2unix
@@ -296,7 +528,6 @@ jobs:
# #
- name: '🔐 Apply dos2unix'
id: task_release_dh_dos2unix
run: |
echo "⚠️⚠️⚠️ Running DOS2UNIX ⚠️⚠️⚠️"
find ./ \( -path "./.git" -o -path "./docs" -o -path "./.github" -o -path "*.png" -o -path "*.jpg" \) -prune -o -name '*' -print | xargs dos2unix --
@@ -307,7 +538,6 @@ jobs:
# #
- name: '#️⃣ Manage Permissions'
id: task_release_dh_permissions
run: |
find ./ -name 'run' -exec chmod 755 {} \;
WRONG_PERM=$(find ./ -path "./.git" -prune -o \( -name "run" -o -name "finish" -o -name "check" \) -not -perm -u=x,g=x,o=x -print)
@@ -397,13 +627,13 @@ jobs:
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=,event=tag
# tag add 1.0.0 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.IMAGE_VERSION }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.PACKAGE_VERSION }}
# tag add 1.0 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.IMAGE_VERSION_2DIGIT }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.PKG_VER_2DIGIT }}
# tag add 1 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=400,prefix=,suffix=,value=${{ env.IMAGE_VERSION_1DIGIT }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=400,prefix=,suffix=,value=${{ env.PKG_VER_1DIGIT }}
# tag add development ( dispatch only + only dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=,value=development
@@ -413,29 +643,33 @@ jobs:
flavor: |
latest=false
labels: |
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=dockerhub
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${GITHUB_SHA1}
annotations: |
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=dockerhub
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${GITHUB_SHA1}
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Dockerhub Build and Push Amd64
@@ -446,21 +680,41 @@ jobs:
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
allow: |
network.host
network: host
context: .
file: Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
labels: ${{ steps.task_release_dh_meta.outputs.labels }}
tags: |
${{ steps.task_release_dh_meta.outputs.tags }}
provenance: false
sbom: false
tags: |
${{ steps.task_release_dh_meta.outputs.tags }}
build-args: |-
ARCH=amd64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
GIT_SHA1=${{ env.GITHUB_SHA1 }}
VERSION=${{ env.PACKAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER }}
GIT_SHA1=${{ env.SHA1 }}
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.architecture=amd64
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-architecture=amd64
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Dockerhub Export Digest Amd64
@@ -499,21 +753,41 @@ jobs:
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
allow: |
network.host
network: host
context: .
file: Dockerfile
platforms: linux/arm64
push: ${{ github.event_name != 'pull_request' }}
labels: ${{ steps.task_release_dh_meta.outputs.labels }}
tags: |
${{ steps.task_release_dh_meta.outputs.tags }}
provenance: false
sbom: false
tags: |
${{ steps.task_release_dh_meta.outputs.tags }}
build-args: |-
ARCH=arm64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
GIT_SHA1=${{ env.GITHUB_SHA1 }}
VERSION=${{ env.PACKAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER }}
GIT_SHA1=${{ env.SHA1 }}
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.architecture=arm64
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-architecture=arm64
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Dockerhub Export Digest Arm64
@@ -548,9 +822,8 @@ jobs:
# #
- name: '⚠️ Checkpoint'
id: task_release_dh_checkpoint
run: |
echo ""
echo ""
echo "---- [ GITHUB ] ----------------------------------------------------------------------------------------"
echo "github.actor.............................. ${{ github.actor }}"
echo "github.ref ............................... ${{ github.ref }}"
@@ -559,46 +832,48 @@ jobs:
echo "github.repository_owner .................. ${{ github.repository_owner }}"
echo "github.repository ........................ ${{ github.repository }}"
echo "github.sha ............................... ${{ github.sha }}"
echo -e ""
echo ""
echo "---- [ INPUTS ] ----------------------------------------------------------------------------------------"
echo "inputs.IMAGE_NAME ........................ ${{ inputs.IMAGE_NAME }}"
echo "inputs.IMAGE_VERSION ..................... ${{ inputs.IMAGE_VERSION }}"
echo "inputs.IMAGE_DOCKERHUB_AUTHOR ............ ${{ inputs.IMAGE_DOCKERHUB_AUTHOR }}"
echo "inputs.IMAGE_DOCKERHUB_USERNAME .......... ${{ inputs.IMAGE_DOCKERHUB_USERNAME }}"
echo "inputs.DEV_RELEASE ....................... ${{ inputs.DEV_RELEASE }}"
echo "inputs.DRY_RUN ........................... ${{ inputs.DRY_RUN }}"
echo -e ""
echo ""
echo "---- [ ENV ] -------------------------------------------------------------------------------------------"
echo "env.IMAGE_NAME ........................... ${{ env.IMAGE_NAME }}"
echo "env.IMAGE_VERSION ........................ ${{ env.IMAGE_VERSION }}"
echo "env.IMAGE_VERSION_1DIGIT ................. ${{ env.IMAGE_VERSION_1DIGIT }}"
echo "env.IMAGE_VERSION_2DIGIT ................. ${{ env.IMAGE_VERSION_2DIGIT }}"
echo "env.PACKAGE_VERSION ...................... ${{ env.PACKAGE_VERSION }}"
echo "env.PKG_VER_1DIGIT ....................... ${{ env.PKG_VER_1DIGIT }}"
echo "env.PKG_VER_2DIGIT ....................... ${{ env.PKG_VER_2DIGIT }}"
echo "env.IMAGE_DOCKERHUB_AUTHOR ............... ${{ env.IMAGE_DOCKERHUB_AUTHOR }}"
echo "env.IMAGE_DOCKERHUB_USERNAME ............. ${{ env.IMAGE_DOCKERHUB_USERNAME }}"
echo "env.NOW .................................. ${{ env.NOW }}"
echo "env.NOW_SHORT ............................ ${{ env.NOW_SHORT }}"
echo "env.NOW_LONG ............................. ${{ env.NOW_LONG }}"
echo "env.NOW_DOCKER_LABEL ..................... ${{ env.NOW_DOCKER_LABEL }}"
echo "env.NOW_DOCKER ........................... ${{ env.NOW_DOCKER }}"
echo "env.NOW_DOCKER_TS ........................ ${{ env.NOW_DOCKER_TS }}"
echo "env.REGISTRY_REPO_ORG_AUTHOR_LC .......... ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}"
echo "env.REGISTRY_REPO_AUTHOR_LC .............. ${{ env.REGISTRY_REPO_AUTHOR_LC }}"
echo "env.DOCKER_SHA ........................... ${{ env.DOCKER_SHA }}"
echo "env.GITHUB_SHA1 .......................... ${{ env.GITHUB_SHA1 }}"
echo -e ""
echo " SHA 1 (GITHUB_SHA) ...................... ${GITHUB_SHA}"
echo " SHA 2 (github.sha) ...................... ${{ github.sha }}"
echo " SHA 3 (env.SHA1) ........................ ${{ env.SHA1 }}"
echo " SHA 4 (env.SHA1_GH) ..................... ${{ env.SHA1_GH }}"
echo ""
echo "---- [ DOCKER IMAGES ] ---------------------------------------------------------------------------------"
echo "registry ................................. Dockerhub"
echo "tags ..................................... ${{ steps.task_release_dh_meta.outputs.tags }}"
echo "labels ................................... ${{ steps.task_release_dh_meta.outputs.labels }}"
echo "digest ................................... ${{ steps.task_release_dh_push_amd64.outputs.digest }}"
echo -e ""
echo ""
echo "(release) tags ........................... ${{ steps.task_release_dh_meta.outputs.tags }}"
echo "(release) labels ......................... ${{ steps.task_release_dh_meta.outputs.labels }}"
echo -e ""
echo ""
echo "---- [ DOCKER DIGESTS ] --------------------------------------------------------------------------------"
echo "docker image id (amd64) .................. ${{ steps.task_release_dh_push_amd64.outputs.imageid }}"
echo "docker digest (amd64) .................... ${{ steps.task_release_dh_push_amd64.outputs.digest }}"
echo "docker image id (arm64) .................. ${{ steps.task_release_dh_push_arm64.outputs.imageid }}"
echo "docker digest (arm64) .................... ${{ steps.task_release_dh_push_arm64.outputs.digest }}"
echo ""
# #
# Release Dockerhub Push Manifest
@@ -621,7 +896,6 @@ jobs:
# #
- name: '🕛 Get Weekly Commit List'
id: task_release_set_weekly_commit_list
run: |
echo 'WEEKLY_COMMITS<<EOF' >> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV
@@ -635,27 +909,27 @@ jobs:
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_RELEASES }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
### 📦 Deploy (Dockerhub) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
### 📦 Deploy (Dockerhub) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
${{ inputs.DEV_RELEASE == true && '### ⚠️⚠️ Development / Pre-release ⚠️⚠️' || '' }}
A new version of the docker container `${{ env.IMAGE_NAME }}` has been released from Github to Dockerhub. The image is available at:
- https://hub.docker.com/r/${{ env.IMAGE_DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}
- Version: `${{ env.IMAGE_VERSION }}`
- Version: `${{ env.PACKAGE_VERSION }}`
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}`
- Pull: `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.PACKAGE_VERSION }}`
- Pull (amd64): `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_dh_push_amd64.outputs.digest }}`
- Pull (arm64): `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_dh_push_arm64.outputs.digest }}`
- Dry Run: `${{ inputs.DRY_RUN }}`
- Source: `Dockerhub` https://hub.docker.com/r/${{ env.IMAGE_DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
@@ -680,4 +954,4 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}

View File

@@ -63,15 +63,18 @@ on:
type: string
# #
# Image Version
# Registry Name
#
# used to create new release tag, and add version to docker image name
# options:
# - github
# - dockerhub
# - gitea
# #
IMAGE_VERSION:
description: '🏷️ Image Version'
IMAGE_REGISTRY:
description: '📘 Registry Name'
required: true
default: '1.0.0'
default: 'gitea'
type: string
# #
@@ -111,6 +114,18 @@ on:
default: 'git.binaryninja.net'
type: string
# #
# Alpine Version
#
# specifies the alpine base docker image version
# #
IMAGE_ALPINE_VERSION:
description: '📀 Alpine Version'
required: true
default: '3.22'
type: string
# #
# true no changes to the repo will be made
# false workflow will behave normally, and push any changes detected to the files
@@ -147,10 +162,15 @@ on:
env:
IMAGE_NAME: ${{ github.event.inputs.IMAGE_NAME || 'tvapp2' }}
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_REGISTRY || 'gitea' }}
IMAGE_GITEA_AUTHOR: ${{ github.event.inputs.IMAGE_GITEA_AUTHOR || 'BinaryNinja' }}
IMAGE_GITEA_USERNAME: ${{ github.event.inputs.IMAGE_GITEA_USERNAME || 'BinaryNinja' }}
IMAGE_GITEA_WEBSITE: ${{ github.event.inputs.IMAGE_GITEA_WEBSITE || 'git.binaryninja.net' }}
IMAGE_ALPINE_VERSION: ${{ github.event.inputs.IMAGE_ALPINE_VERSION || '3.22' }}
DISCORD_BOT_NAME: 'Europa'
DISCORD_BOT_AVATAR: 'https://i.imgur.com/UqwMom1.jpeg'
DISCORD_BOT_EMBED_AUTHOR_ICON: 'https://avatars.githubusercontent.com/u/200161462'
DISCORD_BOT_EMBED_THUMBNAIL: 'https://avatars.githubusercontent.com/u/200161462'
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
@@ -174,9 +194,11 @@ jobs:
job-docker-release-tags-create:
name: >-
📦 Release Create Tag
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 4
outputs:
package_version: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}
permissions:
contents: write
packages: write
@@ -184,25 +206,144 @@ jobs:
id-token: write
steps:
# #
# Release Tags Start
# #
- name: '🏳️ Start'
id: task_release_tags_start
run: |
echo "Creating Tag"
# #
# Release Tags Checkout
# #
- name: ' Checkout'
id: task_release_tags_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Tags Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Release Tags Set Package.json Version
# #
- name: '👁️‍🗨️ Package Version Set'
id: task_initialize_package_getversion
working-directory: ./tvapp2
run: |
VER=$(cat package.json | jq -r '.version')
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_OUTPUT
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_ENV
# #
# Initialize Get Package.json Version
# #
- name: '👁️‍🗨️ Package Version Get'
run: |
echo "VERSION: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}"
# #
# Release Tags Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (env.SHA1) .. ${{ env.SHA1 }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Release Tags Fix Permissions
# #
@@ -232,9 +373,9 @@ jobs:
id: task_release_tags_create
if: ( github.event_name != 'workflow_dispatch' && inputs.DRY_RUN == false )
with:
tag: "${{ env.IMAGE_VERSION }}"
tag: "${{ env.PACKAGE_VERSION }}"
tag_exists_error: false
message: '${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}'
message: '${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}'
gpg_private_key: ${{ secrets.ADMINSERV_GPG_KEY_ASC }}
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
@@ -245,61 +386,152 @@ jobs:
job-docker-release-gitea:
name: >-
📦 Release Gitea
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 10
needs: [ job-docker-release-tags-create ]
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create ]
env:
PACKAGE_VERSION: ${{ needs.job-docker-release-tags-create.outputs.package_version }}
steps:
# #
# Release Gitea Start Amd64
# #
- name: '🏳️ Start'
id: task_release_gi_start
run: |
echo "Starting Gitea docker release"
# #
# Release Gitea Checkout
# #
- name: ' Checkout'
id: task_release_gh_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Gitea Get Timestamp
# Release Gitea Job Information
# #
- name: '🕛 Get Timestamp'
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Release Gitea Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (env.SHA1) .. ${{ env.SHA1 }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Release Gitea Set Vars
# #
- name: '🕛 Set Vars'
id: task_release_set_timestamp
run: |
echo "IMAGE_VERSION_1DIGIT=`echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1`" >> ${GITHUB_ENV} # 1
echo "IMAGE_VERSION_2DIGIT=`echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-2`" >> ${GITHUB_ENV} # 1.0
echo "REGISTRY_REPO_ORG_AUTHOR_LC=`echo ${{ env.IMAGE_GITEA_AUTHOR }}/${{ env.IMAGE_NAME }} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} # thebinaryninja/tvapp2
echo "REGISTRY_REPO_AUTHOR_LC=`echo ${{ env.IMAGE_GITEA_AUTHOR }} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} # thebinaryninja
echo "DOCKER_SHA=${GITHUB_SHA}" >> $GITHUB_ENV # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV # 02-25-2025 12:49:48
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV # 02-25-2025
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV # 02-25-2025 12:49
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV # 20250225
echo "NOW_DOCKER_TS=$(date -u +'%FT%T.%3NZ')" >> $GITHUB_ENV # 2025-02-25T12:50:11.569Z
echo "GITHUB_SHA1=`git rev-parse HEAD`" >> ${GITHUB_ENV}
# #
# Release Gitea Install Dependencies
# #
- name: '📦 Install Dependencies'
id: task_release_gi_dependencies
run:
sudo apt-get install -qq dos2unix
@@ -308,7 +540,6 @@ jobs:
# #
- name: '🔐 Apply dos2unix'
id: task_release_gi_dos2unix
run: |
echo "⚠️⚠️⚠️ Running DOS2UNIX ⚠️⚠️⚠️"
find ./ \( -path "./.git" -o -path "./docs" -o -path "./.github" -o -path "*.png" -o -path "*.jpg" \) -prune -o -name '*' -print | xargs dos2unix --
@@ -319,7 +550,6 @@ jobs:
# #
- name: '#️⃣ Manage Permissions'
id: task_release_gi_permissions
run: |
find ./ -name 'run' -exec chmod 755 {} \;
WRONG_PERM=$(find ./ -path "./.git" -prune -o \( -name "run" -o -name "finish" -o -name "check" \) -not -perm -u=x,g=x,o=x -print)
@@ -341,6 +571,32 @@ jobs:
id: task_release_gi_qemu
uses: docker/setup-qemu-action@v3
# #
# Required to fix insecure SSL error with docker buildx
# #
- name: '⚙️ Configure Docker daemon to allow insecure registry'
run: |
echo "Configuring daemon to treat ${REGISTRY_HOST} as insecure"
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json > /dev/null <<'JSON'
{
"insecure-registries": ["git.binaryninja.net:443"]
}
JSON
# Restart Docker
sudo service docker restart
env:
REGISTRY_HOST: git.binaryninja.net
# #
# Make sure change in docker daemon config successful
# #
- name: '⚙️ Check Docker Daemon Configuration'
run: cat /etc/docker/daemon.json
# #
# Release Gitea Setup BuildX Amd64
# #
@@ -351,6 +607,10 @@ jobs:
with:
version: latest
driver-opts: 'image=moby/buildkit:latest'
driver: docker
buildkitd-flags: --allow-insecure-entitlement
install: true
use: true
# #
# Release Gitea Registry Login Amd64
@@ -410,16 +670,16 @@ jobs:
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-amd64,event=tag
# tag add 1.0.0-amd64 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=500,prefix=,suffix=-amd64,value=${{ env.IMAGE_VERSION }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=500,prefix=,suffix=-amd64,value=${{ env.PACKAGE_VERSION }}
# tag add 1.0.0 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.IMAGE_VERSION }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.PACKAGE_VERSION }}
# tag add 1.0 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.IMAGE_VERSION_2DIGIT }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.PKG_VER_2DIGIT }}
# tag add 1 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=400,prefix=,suffix=,value=${{ env.IMAGE_VERSION_1DIGIT }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=400,prefix=,suffix=,value=${{ env.PKG_VER_1DIGIT }}
# dispatch add development-amd64 ( dispatch only + only dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=development-,suffix=,value=amd64
@@ -429,29 +689,33 @@ jobs:
flavor: |
latest=false
labels: |
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=gitea
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${GITHUB_SHA1}
annotations: |
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=gitea
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${GITHUB_SHA1}
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Gitea Meta arm64
@@ -474,36 +738,40 @@ jobs:
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-arm64,event=tag
# tag add 1.0.0-arm64 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=500,prefix=,suffix=-arm64,value=${{ env.IMAGE_VERSION }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=500,prefix=,suffix=-arm64,value=${{ env.PACKAGE_VERSION }}
# dispatch add development-arm64 ( dispatch only + only dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-arm64,value=development
flavor: |
latest=false
labels: |
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=gitea
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${GITHUB_SHA1}
annotations: |
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=gitea
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${GITHUB_SHA1}
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Gitea Meta Release
@@ -529,13 +797,13 @@ jobs:
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=,event=tag
# tag add 1.0.0 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.IMAGE_VERSION }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.PACKAGE_VERSION }}
# tag add 1.0 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.IMAGE_VERSION_2DIGIT }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.PKG_VER_2DIGIT }}
# tag add 1 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=400,prefix=,suffix=,value=${{ env.IMAGE_VERSION_1DIGIT }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=400,prefix=,suffix=,value=${{ env.PKG_VER_1DIGIT }}
# tag add development ( dispatch only + only dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=,value=development
@@ -545,25 +813,33 @@ jobs:
flavor: |
latest=false
labels: |
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=gitea
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
annotations: |
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=gitea
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Gitea Build and Push Amd64
@@ -574,21 +850,41 @@ jobs:
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
allow: |
network.host
network: host
context: .
file: Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
labels: ${{ steps.task_release_gi_meta_amd64.outputs.labels }}
tags: |
${{ steps.task_release_gi_meta_amd64.outputs.tags }}
provenance: false
sbom: false
tags: |
${{ steps.task_release_gi_meta_amd64.outputs.tags }}
build-args: |-
ARCH=amd64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
GIT_SHA1=${{ env.GITHUB_SHA1 }}
VERSION=${{ env.PACKAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER }}
GIT_SHA1=${{ env.SHA1 }}
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.architecture=amd64
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-architecture=amd64
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Gitea Export Digest Amd64
@@ -627,21 +923,41 @@ jobs:
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
allow: |
network.host
network: host
context: .
file: Dockerfile
platforms: linux/arm64
push: ${{ github.event_name != 'pull_request' }}
labels: ${{ steps.task_release_gi_meta_arm64.outputs.labels }}
tags: |
${{ steps.task_release_gi_meta_arm64.outputs.tags }}
provenance: false
sbom: false
tags: |
${{ steps.task_release_gi_meta_arm64.outputs.tags }}
build-args: |-
ARCH=arm64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
GIT_SHA1=${{ env.GITHUB_SHA1 }}
VERSION=${{ env.PACKAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER }}
GIT_SHA1=${{ env.SHA1 }}
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.architecture=arm64
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-architecture=arm64
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Gitea Export Digest Arm64
@@ -676,9 +992,8 @@ jobs:
# #
- name: '⚠️ Checkpoint'
id: task_release_gi_checkpoint
run: |
echo ""
echo ""
echo "---- [ GITHUB ] ----------------------------------------------------------------------------------------"
echo "github.actor.............................. ${{ github.actor }}"
echo "github.ref ............................... ${{ github.ref }}"
@@ -687,52 +1002,54 @@ jobs:
echo "github.repository_owner .................. ${{ github.repository_owner }}"
echo "github.repository ........................ ${{ github.repository }}"
echo "github.sha ............................... ${{ github.sha }}"
echo -e ""
echo ""
echo "---- [ INPUTS ] ----------------------------------------------------------------------------------------"
echo "inputs.IMAGE_NAME ........................ ${{ inputs.IMAGE_NAME }}"
echo "inputs.IMAGE_VERSION ..................... ${{ inputs.IMAGE_VERSION }}"
echo "inputs.IMAGE_GITEA_USERNAME .............. ${{ inputs.IMAGE_GITEA_AUTHOR }}"
echo "inputs.IMAGE_GITEA_USERNAME .............. ${{ inputs.IMAGE_GITEA_USERNAME }}"
echo "inputs.IMAGE_GITEA_WEBSITE ............... ${{ inputs.IMAGE_GITEA_WEBSITE }}"
echo "inputs.DEV_RELEASE ....................... ${{ inputs.DEV_RELEASE }}"
echo "inputs.DRY_RUN ........................... ${{ inputs.DRY_RUN }}"
echo -e ""
echo ""
echo "---- [ ENV ] -------------------------------------------------------------------------------------------"
echo "env.IMAGE_NAME ........................... ${{ env.IMAGE_NAME }}"
echo "env.IMAGE_VERSION ........................ ${{ env.IMAGE_VERSION }}"
echo "env.IMAGE_VERSION_1DIGIT ................. ${{ env.IMAGE_VERSION_1DIGIT }}"
echo "env.IMAGE_VERSION_2DIGIT ................. ${{ env.IMAGE_VERSION_2DIGIT }}"
echo "env.PACKAGE_VERSION ...................... ${{ env.PACKAGE_VERSION }}"
echo "env.PKG_VER_1DIGIT ....................... ${{ env.PKG_VER_1DIGIT }}"
echo "env.PKG_VER_2DIGIT ....................... ${{ env.PKG_VER_2DIGIT }}"
echo "env.IMAGE_GITEA_AUTHOR ................... ${{ env.IMAGE_GITEA_AUTHOR }}"
echo "env.IMAGE_GITEA_USERNAME ................. ${{ env.IMAGE_GITEA_USERNAME }}"
echo "env.IMAGE_GITEA_WEBSITE .................. ${{ env.IMAGE_GITEA_WEBSITE }}"
echo "env.NOW .................................. ${{ env.NOW }}"
echo "env.NOW_SHORT ............................ ${{ env.NOW_SHORT }}"
echo "env.NOW_LONG ............................. ${{ env.NOW_LONG }}"
echo "env.NOW_DOCKER_LABEL ..................... ${{ env.NOW_DOCKER_LABEL }}"
echo "env.NOW_DOCKER ........................... ${{ env.NOW_DOCKER }}"
echo "env.NOW_DOCKER_TS ........................ ${{ env.NOW_DOCKER_TS }}"
echo "env.REGISTRY_REPO_ORG_AUTHOR_LC .......... ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}"
echo "env.REGISTRY_REPO_AUTHOR_LC .............. ${{ env.REGISTRY_REPO_AUTHOR_LC }}"
echo "env.DOCKER_SHA ........................... ${{ env.DOCKER_SHA }}"
echo "env.GITHUB_SHA1 .......................... ${{ env.GITHUB_SHA1 }}"
echo -e ""
echo " SHA 1 (GITHUB_SHA) ...................... ${GITHUB_SHA}"
echo " SHA 2 (github.sha) ...................... ${{ github.sha }}"
echo " SHA 3 (env.SHA1) ........................ ${{ env.SHA1 }}"
echo " SHA 4 (env.SHA1_GH) ..................... ${{ env.SHA1_GH }}"
echo ""
echo "---- [ DOCKER IMAGES ] ---------------------------------------------------------------------------------"
echo "registry ................................. Gitea"
echo "(amd64) tags ............................. ${{ steps.task_release_gi_meta_amd64.outputs.tags }}"
echo "(amd64) labels ........................... ${{ steps.task_release_gi_meta_amd64.outputs.labels }}"
echo "(amd64) digest ........................... ${{ steps.task_release_gi_push_amd64.outputs.digest }}"
echo ""
echo ""
echo "(arm64) tags ............................. ${{ steps.task_release_gi_meta_arm64.outputs.tags }}"
echo "(arm64) labels ........................... ${{ steps.task_release_gi_meta_arm64.outputs.labels }}"
echo "(arm64) digest ........................... ${{ steps.task_release_gi_push_arm64.outputs.digest }}"
echo ""
echo ""
echo "(release) tags ........................... ${{ steps.task_release_gi_meta_release.outputs.tags }}"
echo "(release) labels ......................... ${{ steps.task_release_gi_meta_release.outputs.labels }}"
echo -e ""
echo ""
echo "---- [ DOCKER DIGESTS ] --------------------------------------------------------------------------------"
echo "docker image id (amd64) .................. ${{ steps.task_release_gi_push_amd64.outputs.imageid }}"
echo "docker digest (amd64) .................... ${{ steps.task_release_gi_push_amd64.outputs.digest }}"
echo "docker image id (arm64) .................. ${{ steps.task_release_gi_push_arm64.outputs.imageid }}"
echo "docker digest (arm64) .................... ${{ steps.task_release_gi_push_arm64.outputs.digest }}"
echo ""
# #
# Release Gitea Push Manifest
@@ -755,7 +1072,6 @@ jobs:
# #
- name: '🕛 Get Weekly Commit List'
id: task_release_set_weekly_commit_list
run: |
echo 'WEEKLY_COMMITS<<EOF' >> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV
@@ -769,27 +1085,27 @@ jobs:
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_RELEASES }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
### 📦 Deploy (Gitea) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
### 📦 Deploy (Gitea) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
${{ inputs.DEV_RELEASE == true && '### ⚠️⚠️ Development / Pre-release ⚠️⚠️' || '' }}
A new version of the docker container `${{ env.IMAGE_NAME }}` has been released from Github to Gitea. The image is available at:
- https://${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
- Version: `${{ env.IMAGE_VERSION }}`
- Version: `${{ env.PACKAGE_VERSION }}`
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}`
- Pull: `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.PACKAGE_VERSION }}`
- Pull (amd64): `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gi_push_amd64.outputs.digest }}`
- Pull (arm64): `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gi_push_arm64.outputs.digest }}`
- Dry Run: `${{ inputs.DRY_RUN }}`
- Source: `Gitea` https://${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
@@ -814,4 +1130,4 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}

View File

@@ -31,8 +31,8 @@
# act -W .github/workflows/deploy-docker-github.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "📦 Deploy Docker Github"
run-name: "📦 Deploy Docker Github"
name: '📦 Deploy Docker Github'
run-name: '📦 Deploy Docker Github'
# #
# Triggers
@@ -63,15 +63,18 @@ on:
type: string
# #
# Image Version
# Registry Name
#
# used to create new release tag, and add version to docker image name
# options:
# - github
# - dockerhub
# - gitea
# #
IMAGE_VERSION:
description: '🏷️ Image Version'
IMAGE_REGISTRY:
description: '📘 Registry Name'
required: true
default: '1.0.0'
default: 'github'
type: string
# #
@@ -99,6 +102,18 @@ on:
default: 'TheBinaryNinja'
type: string
# #
# Alpine Version
#
# specifies the alpine base docker image version
# #
IMAGE_ALPINE_VERSION:
description: '📀 Alpine Version'
required: true
default: '3.22'
type: string
# #
# true no changes to the repo will be made
# false workflow will behave normally, and push any changes detected to the files
@@ -135,9 +150,14 @@ on:
env:
IMAGE_NAME: ${{ github.event.inputs.IMAGE_NAME || 'tvapp2' }}
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
IMAGE_REGISTRY: ${{ github.event.inputs.IMAGE_REGISTRY || 'github' }}
IMAGE_GHCR_AUTHOR: ${{ github.event.inputs.IMAGE_GHCR_AUTHOR || 'BinaryNinja' }}
IMAGE_GHCR_USERNAME: ${{ github.event.inputs.IMAGE_GHCR_USERNAME || 'BinaryNinja' }}
IMAGE_ALPINE_VERSION: ${{ github.event.inputs.IMAGE_ALPINE_VERSION || '3.22' }}
DISCORD_BOT_NAME: 'Europa'
DISCORD_BOT_AVATAR: 'https://i.imgur.com/UqwMom1.jpeg'
DISCORD_BOT_EMBED_AUTHOR_ICON: 'https://avatars.githubusercontent.com/u/200161462'
DISCORD_BOT_EMBED_THUMBNAIL: 'https://avatars.githubusercontent.com/u/200161462'
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
@@ -161,9 +181,11 @@ jobs:
job-docker-release-tags-create:
name: >-
📦 Release Create Tag
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 4
outputs:
package_version: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}
permissions:
contents: write
packages: write
@@ -171,31 +193,149 @@ jobs:
id-token: write
steps:
# #
# Release Tags Start
# #
- name: '🏳️ Start'
id: task_release_tags_start
run: |
echo "Creating Tag"
# #
# Release Tags Checkout
# #
- name: ' Checkout'
id: task_release_tags_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Tags Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Release Tags Set Package.json Version
# #
- name: '👁️‍🗨️ Package Version Set'
id: task_initialize_package_getversion
working-directory: ./tvapp2
run: |
VER=$(cat package.json | jq -r '.version')
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_OUTPUT
echo "PACKAGE_VERSION=${VER}" >> $GITHUB_ENV
# #
# Initialize Get Package.json Version
# #
- name: '👁️‍🗨️ Package Version Get'
run: |
echo "VERSION: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}"
# #
# Release Tags Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Release Tags Fix Permissions
# #
- name: '#️⃣ Manage Permissions'
id: task_release_tags_permissions
run: |
find ./ -name 'run' -exec chmod 755 {} \;
WRONG_PERM=$(find ./ -path "./.git" -prune -o \( -name "run" -o -name "finish" -o -name "check" \) -not -perm -u=x,g=x,o=x -print)
@@ -210,18 +350,17 @@ jobs:
fi
# #
# Release Tags Create Tag
# Tags Tags Create Tag
#
# only called in dispatch mode
# #
- uses: rickstaa/action-create-tag@v1
id: task_release_tags_create
if: ( github.event_name != 'workflow_dispatch' && inputs.DRY_RUN == false )
with:
tag: "${{ env.IMAGE_VERSION }}"
tag: "${{ env.PACKAGE_VERSION }}"
tag_exists_error: false
message: '${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}'
message: '${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}'
gpg_private_key: ${{ secrets.ADMINSERV_GPG_KEY_ASC }}
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
@@ -232,59 +371,151 @@ jobs:
job-docker-release-github:
name: >-
📦 Release Github
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 10
needs: [ job-docker-release-tags-create ]
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create ]
env:
PACKAGE_VERSION: ${{ needs.job-docker-release-tags-create.outputs.package_version }}
steps:
# #
# Release Github Start Amd64
# Release Github Checkout
# #
- name: '🏳 Start'
id: task_release_gh_start
run: |
echo "Starting Github docker release"
# #
# Release Github Checkout Amd64
# #
- name: '✅ Checkout'
id: task_release_gh_checkout
- name: ' Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Github Get Timestamp
# Release Github Job Information
# #
- name: '🕛 Get Timestamp'
id: task_release_set_timestamp
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Release Github Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.PACKAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Release Github Set Vars
# #
- name: '🕛 Set Vars'
run: |
echo "IMAGE_VERSION_1DIGIT=`echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1`" >> ${GITHUB_ENV} # 1
echo "IMAGE_VERSION_2DIGIT=`echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-2`" >> ${GITHUB_ENV} # 1.0
echo "REGISTRY_REPO_ORG_AUTHOR_LC=`echo ${{ env.IMAGE_GHCR_AUTHOR }}/${{ env.IMAGE_NAME }} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} # thebinaryninja/tvapp2
echo "REGISTRY_REPO_AUTHOR_LC=`echo ${{ env.IMAGE_GHCR_AUTHOR }} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} # thebinaryninja
echo "DOCKER_SHA=${GITHUB_SHA}" >> $GITHUB_ENV # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV # 02-25-2025 12:49:48
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV # 02-25-2025
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV # 02-25-2025 12:49
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV # 20250225
echo "NOW_DOCKER_TS=$(date -u +'%FT%T.%3NZ')" >> $GITHUB_ENV # 2025-02-25T12:50:11.569Z
echo "GITHUB_SHA1=`git rev-parse HEAD`" >> ${GITHUB_ENV}
# #
# Release Github Install Dependencies
# #
- name: '📦 Install Dependencies'
id: task_release_gh_dependencies
run:
sudo apt-get install -qq dos2unix
@@ -293,7 +524,6 @@ jobs:
# #
- name: '🔐 Apply dos2unix'
id: task_release_gh_dos2unix
run: |
echo "⚠️⚠️⚠️ Running DOS2UNIX ⚠️⚠️⚠️"
find ./ \( -path "./.git" -o -path "./docs" -o -path "./.github" -o -path "*.png" -o -path "*.jpg" \) -prune -o -name '*' -print | xargs dos2unix --
@@ -304,7 +534,6 @@ jobs:
# #
- name: '#️⃣ Manage Permissions'
id: task_release_gh_permissions
run: |
find ./ -name 'run' -exec chmod 755 {} \;
WRONG_PERM=$(find ./ -path "./.git" -prune -o \( -name "run" -o -name "finish" -o -name "check" \) -not -perm -u=x,g=x,o=x -print)
@@ -323,7 +552,6 @@ jobs:
# #
- name: '⚙️ Set up QEMU'
id: task_release_gh_qemu
uses: docker/setup-qemu-action@v3
# #
@@ -331,7 +559,6 @@ jobs:
# #
- name: '⚙️ Setup Buildx'
id: task_release_gh_buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
@@ -342,7 +569,6 @@ jobs:
# #
- name: '⚙️ Login to Github'
id: task_release_gh_registry
uses: docker/login-action@v3
with:
registry: ghcr.io
@@ -365,7 +591,8 @@ jobs:
const { promises: fs } = require('fs')
const main = async () => {
const main = async () =>
{
const path = "README.md"
let content = await fs.readFile(path, 'utf8')
@@ -395,13 +622,13 @@ jobs:
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=,event=tag
# tag add 1.0.0 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.IMAGE_VERSION }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=450,prefix=,suffix=,value=${{ env.PACKAGE_VERSION }}
# tag add 1.0 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.IMAGE_VERSION_2DIGIT }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=425,prefix=,suffix=,value=${{ env.PKG_VER_2DIGIT }}
# tag add 1 ( dispatch only + no dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=400,prefix=,suffix=,value=${{ env.IMAGE_VERSION_1DIGIT }}
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=400,prefix=,suffix=,value=${{ env.PKG_VER_1DIGIT }}
# tag add development ( dispatch only + only dev )
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=,value=development
@@ -411,29 +638,33 @@ jobs:
flavor: |
latest=false
labels: |
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=github
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${GITHUB_SHA1}
annotations: |
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=github
org.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${GITHUB_SHA1}
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Github Build and Push Amd64
@@ -450,27 +681,41 @@ jobs:
context: .
file: Dockerfile
platforms: linux/amd64
provenance: false
sbom: false
push: ${{ github.event_name != 'pull_request' }}
labels: ${{ steps.task_release_gh_meta.outputs.labels }}
tags: |
${{ steps.task_release_gh_meta.outputs.tags }}
annotations: |
${{ steps.task_release_gh_meta.outputs.annotations }}
provenance: false
sbom: false
build-args: |-
ARCH=amd64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
GIT_SHA1=${{ env.GITHUB_SHA1 }}
VERSION=${{ env.PACKAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER }}
GIT_SHA1=${{ env.SHA1 }}
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.architecture=amd64
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-architecture=amd64
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Github Export Digest Amd64
# #
- name: '📄 Export Digest (linux/amd64)'
id: task_release_gh_digest_export_amd64
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
run: |
mkdir -p /tmp/build-digest-amd64
@@ -484,7 +729,6 @@ jobs:
# #
- name: '🔼 Upload Digest (linux/amd64)'
id: task_release_gh_digest_upload_amd64
uses: actions/upload-artifact@v4
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
@@ -508,27 +752,41 @@ jobs:
context: .
file: Dockerfile
platforms: linux/arm64
provenance: false
sbom: false
push: ${{ github.event_name != 'pull_request' }}
labels: ${{ steps.task_release_gh_meta.outputs.labels }}
tags: |
${{ steps.task_release_gh_meta.outputs.tags }}
annotations: |
${{ steps.task_release_gh_meta.outputs.annotations }}
provenance: false
sbom: false
build-args: |-
ARCH=arm64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
GIT_SHA1=${{ env.GITHUB_SHA1 }}
VERSION=${{ env.PACKAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER }}
GIT_SHA1=${{ env.SHA1 }}
ALPINE_VERSION=${{ env.IMAGE_ALPINE_VERSION }}
annotations: |-
org.opencontainers.image.description=TVApp2
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.PACKAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.architecture=arm64
org.opencontainers.image.revision=${{ env.SHA1 }}
org.opencontainers.image.vendor=${{ env.REGISTRY_REPO_AUTHOR_LC }}
org.opencontainers.image.ref.name=${{ github.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
org.opencontainers.image.registry=${{ env.IMAGE_REGISTRY }}
org.tvapp2.image.build-version="Version: ${{ env.PACKAGE_VERSION }} Date: ${{ env.NOW_DOCKER }}"
org.tvapp2.image.build-version-alpine=${{ env.IMAGE_ALPINE_VERSION }}
org.tvapp2.image.build-architecture=arm64
org.tvapp2.image.build-release="${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}"
org.tvapp2.image.build-sha1=${{ env.SHA1 }}
# #
# Release Github Export Digest Arm64
# #
- name: '📄 Export Digest (linux/arm64)'
id: task_release_gh_digest_export_arm64
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
run: |
mkdir -p /tmp/build-digest-arm64
@@ -542,7 +800,6 @@ jobs:
# #
- name: '🔼 Upload Digest (linux/arm64)'
id: task_release_gh_digest_upload_arm64
uses: actions/upload-artifact@v4
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
@@ -556,9 +813,8 @@ jobs:
# #
- name: '⚠️ Checkpoint'
id: task_release_gh_checkpoint
run: |
echo ""
echo ""
echo "---- [ GITHUB ] ----------------------------------------------------------------------------------------"
echo "github.actor.............................. ${{ github.actor }}"
echo "github.ref ............................... ${{ github.ref }}"
@@ -567,53 +823,54 @@ jobs:
echo "github.repository_owner .................. ${{ github.repository_owner }}"
echo "github.repository ........................ ${{ github.repository }}"
echo "github.sha ............................... ${{ github.sha }}"
echo -e ""
echo ""
echo "---- [ INPUTS ] ----------------------------------------------------------------------------------------"
echo "inputs.IMAGE_NAME ........................ ${{ inputs.IMAGE_NAME }}"
echo "inputs.IMAGE_VERSION ..................... ${{ inputs.IMAGE_VERSION }}"
echo "inputs.IMAGE_GHCR_AUTHOR ................. ${{ inputs.IMAGE_GHCR_AUTHOR }}"
echo "inputs.IMAGE_GHCR_USERNAME ............... ${{ inputs.IMAGE_GHCR_USERNAME }}"
echo "inputs.DEV_RELEASE ....................... ${{ inputs.DEV_RELEASE }}"
echo "inputs.DRY_RUN ........................... ${{ inputs.DRY_RUN }}"
echo -e ""
echo ""
echo "---- [ ENV ] -------------------------------------------------------------------------------------------"
echo "env.IMAGE_NAME ........................... ${{ env.IMAGE_NAME }}"
echo "env.IMAGE_VERSION ........................ ${{ env.IMAGE_VERSION }}"
echo "env.IMAGE_VERSION_1DIGIT ................. ${{ env.IMAGE_VERSION_1DIGIT }}"
echo "env.IMAGE_VERSION_2DIGIT ................. ${{ env.IMAGE_VERSION_2DIGIT }}"
echo "env.PACKAGE_VERSION ...................... ${{ env.PACKAGE_VERSION }}"
echo "env.PKG_VER_1DIGIT ....................... ${{ env.PKG_VER_1DIGIT }}"
echo "env.PKG_VER_2DIGIT ....................... ${{ env.PKG_VER_2DIGIT }}"
echo "env.IMAGE_GHCR_AUTHOR .................... ${{ env.IMAGE_GHCR_AUTHOR }}"
echo "env.IMAGE_GHCR_USERNAME .................. ${{ env.IMAGE_GHCR_USERNAME }}"
echo "env.NOW .................................. ${{ env.NOW }}"
echo "env.NOW_SHORT ............................ ${{ env.NOW_SHORT }}"
echo "env.NOW_LONG ............................. ${{ env.NOW_LONG }}"
echo "env.NOW_DOCKER_LABEL ..................... ${{ env.NOW_DOCKER_LABEL }}"
echo "env.NOW_DOCKER ........................... ${{ env.NOW_DOCKER }}"
echo "env.NOW_DOCKER_TS ........................ ${{ env.NOW_DOCKER_TS }}"
echo "env.REGISTRY_REPO_ORG_AUTHOR_LC .......... ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}"
echo "env.REGISTRY_REPO_AUTHOR_LC .............. ${{ env.REGISTRY_REPO_AUTHOR_LC }}"
echo "env.DOCKER_SHA ........................... ${{ env.DOCKER_SHA }}"
echo "env.GITHUB_SHA1 .......................... ${{ env.GITHUB_SHA1 }}"
echo -e ""
echo " SHA 1 (GITHUB_SHA) ...................... ${GITHUB_SHA}"
echo " SHA 2 (github.sha) ...................... ${{ github.sha }}"
echo " SHA 3 (env.SHA1) ........................ ${{ env.SHA1 }}"
echo " SHA 4 (env.SHA1_GH) ..................... ${{ env.SHA1_GH }}"
echo ""
echo "---- [ DOCKER IMAGES ] ---------------------------------------------------------------------------------"
echo "registry ................................. Github GHCR"
echo "tags ..................................... ${{ steps.task_release_gh_meta.outputs.tags }}"
echo "labels ................................... ${{ steps.task_release_gh_meta.outputs.labels }}"
echo "digest ................................... ${{ steps.task_release_gh_push_amd64.outputs.digest }}"
echo -e ""
echo ""
echo "(release) tags ........................... ${{ steps.task_release_gh_meta.outputs.tags }}"
echo "(release) labels ......................... ${{ steps.task_release_gh_meta.outputs.labels }}"
echo -e ""
echo ""
echo "---- [ DOCKER DIGESTS ] --------------------------------------------------------------------------------"
echo "docker image id (amd64) .................. ${{ steps.task_release_gh_push_amd64.outputs.imageid }}"
echo "docker digest (amd64) .................... ${{ steps.task_release_gh_push_amd64.outputs.digest }}"
echo "docker image id (arm64) .................. ${{ steps.task_release_gh_push_arm64.outputs.imageid }}"
echo "docker digest (arm64) .................... ${{ steps.task_release_gh_push_arm64.outputs.digest }}"
echo ""
# #
# Release Github Push Manifest
# #
- name: '📦 Push Manifest'
id: task_release_gh_manifest
uses: int128/docker-manifest-create-action@v2
with:
push: ${{ !inputs.DRY_RUN }}
@@ -630,7 +887,6 @@ jobs:
# #
- name: '🕛 Get Weekly Commit List'
id: task_release_set_weekly_commit_list
run: |
echo 'WEEKLY_COMMITS<<EOF' >> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV
@@ -644,27 +900,27 @@ jobs:
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_RELEASES }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
### 📦 Deploy (Github) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
### 📦 Deploy (Github) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
${{ inputs.DEV_RELEASE == true && '### ⚠️⚠️ Development / Pre-release ⚠️⚠️' || '' }}
A new version of the docker container `${{ env.IMAGE_NAME }}` has been released from Github to Github GHCR. The image is available at:
- https://github.com/${{ github.repository }}/pkgs/container/${{ env.IMAGE_NAME }}
- Version: `${{ env.IMAGE_VERSION }}`
- Version: `${{ env.PACKAGE_VERSION }}`
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}`
- Pull: `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.PACKAGE_VERSION }}`
- Pull (amd64): `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gh_push_amd64.outputs.digest }}`
- Pull (arm64): `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gh_push_arm64.outputs.digest }}`
- Dry Run: `${{ inputs.DRY_RUN }}`
- Source: `Github` https://github.com/${{ github.repository }}
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.PACKAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
@@ -689,7 +945,7 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}
# #
# Job Docker Release Cleanup
@@ -698,42 +954,41 @@ jobs:
job-docker-release-cleanup:
name: >-
🧹 Release Cleanup
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
needs: [ job-docker-release-tags-create, job-docker-release-github ]
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create, job-docker-release-github ]
env:
PACKAGE_VERSION: ${{ needs.job-docker-release-tags-create.outputs.package_version }}
steps:
# #
# Release Cleanup
# #
- name: '🏳️ Start'
id: task_cleanup_gh_start
run: |
echo "Cleaning up untagged docker images"
# #
# Release Cleanup Checkout
# #
- name: '✅ Checkout'
id: task_cleanup_gh_checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Cleanup Print Version Debug
# #
- name: '🪪 Get Package Version'
run: |
echo "VERSION: ${{ env.PACKAGE_VERSION }}"
# #
# Release Cleanup Clean Untagged Images
# #
- name: '🧹 Clean Untagged Images'
id: task_cleanup_clean
uses: quartx-analytics/ghcr-cleaner@v1
with:
owner-type: org

View File

@@ -15,15 +15,20 @@
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.ORG_BINARYNINJA_TOKEN org github personal access token (fine-grained)
# secrets.ORG_BINARYNINJA_TOKEN_CL org github personal access token (classic)
# secrets.ORG_BINARYNINJA_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_BINARYNINJA_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BINARYSERV_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BINARYSERV_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKFLOWS discord webhook to report workflow notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_UPDATES discord webhook to report activity notifications from github to discord
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_UPDATES discord webhook to report activity notifications from github to discord
#
# @local these workflows can be tested locally through the use of `act`
# https://github.com/nektos/act
@@ -35,8 +40,8 @@
# act -W .github/workflows/documentation.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "📒 Docs Build"
run-name: "📒 Docs Build"
name: '📒 Docs Build'
run-name: '📒 Docs Build'
# #
# triggers
@@ -86,14 +91,62 @@ on:
default: './docs/site'
type: string
# #
# Discord Bot Name
#
# The discord bot name
# #
DISCORD_BOT_NAME:
description: '🤖 Bot Name'
required: true
default: 'Europa'
type: string
# #
# Discord Bot Avatar
#
# The discord bot avatar to show; let's use some weird picture
# #
DISCORD_BOT_AVATAR:
description: '🤖 Avatar URL'
required: true
default: 'https://i.imgur.com/UqwMom1.jpeg'
type: string
# #
# Discord Bot Author Icon URL
#
# A small picture shown to the top-right of each post
# #
DISCORD_BOT_EMBED_AUTHOR_ICON:
description: '🤖 Embed Author Icon'
required: true
default: 'https://avatars.githubusercontent.com/u/200161462'
type: string
# #
# Discord Bot Thumbnail URL
#
# A small picture shown to the top-right of each post
# #
DISCORD_BOT_EMBED_THUMBNAIL:
description: '🤖 Embed Thumbnail URL'
required: true
default: 'https://avatars.githubusercontent.com/u/200161462'
type: string
# #
# Trigger Cron
#
# update documentation every X hours
# #
schedule:
- cron: "0 */12 * * *"
# schedule:
# - cron: "0 */12 * * *"
# #
# environment variables
@@ -101,6 +154,10 @@ on:
env:
WORKING_DIR: ${{ github.event.inputs.WORKING_DIR || './docs/site' }}
DISCORD_BOT_NAME: ${{ github.event.inputs.DISCORD_BOT_NAME || 'Europa' }}
DISCORD_BOT_AVATAR: ${{ github.event.inputs.DISCORD_BOT_AVATAR || 'https://i.imgur.com/UqwMom1.jpeg' }}
DISCORD_BOT_EMBED_AUTHOR_ICON: ${{ github.event.inputs.DISCORD_BOT_EMBED_AUTHOR_ICON || 'https://avatars.githubusercontent.com/u/200161462' }}
DISCORD_BOT_EMBED_THUMBNAIL: ${{ github.event.inputs.DISCORD_BOT_EMBED_THUMBNAIL || 'https://avatars.githubusercontent.com/u/200161462' }}
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
@@ -113,9 +170,9 @@ env:
jobs:
build-docs:
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 20
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 10
permissions:
contents: write
pages: write
@@ -124,45 +181,139 @@ jobs:
steps:
# #
# Documentation Checkout Arm64
# Documentation Build Checkout
# #
- name: >-
✅ Checkout
id: task_docs_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Documentation Set Env Variables
# Documentation Build Job Information
# #
- name: >-
🕛 Get Timestamp
id: task_docs_set_timestamp
run: |
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Documentation Setup Python
# Documentation Build Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Documentation Build Setup Python
# #
- name: >-
🐍 Setup Python
id: task_docs_python_setup
uses: actions/setup-python@v5
with:
python-version: 3.x
# #
# Documentation Build Documentation
# Documentation Build Build
# #
- name: >-
📕 Build Documentation
id: task_docs_build
run: |
export DOCS_NAME=${{ secrets.DOCS_NAME || 'TVApp2' }}
export DOCS_SECRET_L1=${{ secrets.DOCS_SECRET_L1 }}
@@ -183,12 +334,11 @@ jobs:
GH_TOKEN: ${{ secrets.ADMINSERV_TOKEN }}
# #
# Documentation Deploy
# Documentation Build Deploy
# #
- name: >-
💽 Deploy
id: task_docs_deploy
uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.ADMINSERV_TOKEN_CL }}
@@ -196,33 +346,31 @@ jobs:
# #
# Documentation Get Weekly Commits
# Documentation Build Get Weekly Commits
# #
- name: >-
🕛 Get Weekly Commit List
id: task_docs_set_weekly_commit_list
run: |
echo 'WEEKLY_COMMITS<<EOF' >> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
# #
# Documentation Notify Github Success
# Documentation Build Notify Github Success
# #
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_docs_notify_discord_success
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## 📦 Documentation Deployment${{ job.status == 'success' && '✅' || '❌' }}
@@ -239,24 +387,23 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}
# #
# Documentation Notify Github Failure
# Documentation Build Notify Github Failure
# #
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_docs_notify_discord_failure
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## 📦 Documentation Deployment${{ job.status == 'success' && '✅' || '❌' }}
@@ -273,5 +420,5 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}

366
.github/workflows/gpg-tests.yml vendored Normal file
View File

@@ -0,0 +1,366 @@
# #
# @type github workflow
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage tests gpg keys
#
# @secrets secrets.SELF_TOKEN self github personal access token (fine-grained)
# secrets.SELF_TOKEN_CL self github personal access token (classic)
# secrets.NPM_TOKEN self npmjs access token
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_UPDATES discord webhook to report activity notifications from github to discord
#
# @local these workflows can be tested locally through the use of `act`
# https://github.com/nektos/act
# Extract act to folder
# Add system env var with path to act.exe
# Run the commands:
# git pull https://github.com/username/repo
# act -W .github/workflows/gpg-tests.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/gpg-tests.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: '🔑 GPG Tests'
run-name: '🔑 GPG Tests'
# #
# triggers
# #
on:
workflow_dispatch:
inputs:
# #
# Name of the plugin to use when creating the release zip filename
# e.g: ntfy-desktop-v1.0.0.zip
# #
PROJECT_NAME:
description: '📦 Name of App'
required: true
default: 'ntfy-desktop'
type: string
# #
# environment variables
# #
env:
PROJECT_NAME: ${{ github.event.inputs.PROJECT_NAME || 'ntfy-desktop' }}
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
GPG_KEY_BASE64: ${{ secrets.ADMINSERV_GPG_KEY_B64 }}
GPG_KEY_PASSPHRASE: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
# #
# Jobs
# #
jobs:
# #
# JOB > INITIALIZE
# #
job-initialize:
name: >-
🔑 GPG Tests
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
outputs:
package_version: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}
permissions:
contents: write
packages: write
steps:
# #
# GPG Checkout
# #
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# GPG Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# GPG Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# GPG Get version from package.json VERSION value
# #
- name: '👁️‍🗨️ Get Package Version'
id: task_initialize_package_getversion
run: |
VER=$(cat package.json | jq -r '.version')
echo "PACKAGE_VERSION=$VER" >> $GITHUB_OUTPUT
- name: '👁️‍🗨️ Get Package Version Print'
id: task_initialize_package_getversion_debug
run: |
echo "VERSION: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}"
# #
# GPG Import Key (No Passphrase)
#
# requires your GPG private key, converted to base64 binary .gpg (not armored .asc)
# #
- name: '🪪 GPG Import Signing Key W/o Passphrase'
if: env.GPG_KEY_BASE64 != '' && env.GPG_KEY_PASSPHRASE == ''
run: |
echo "$GPG_KEY_BASE64" | base64 -di | gpg --import
# #
# GPG Import Key (With Passphrase)
#
# requires your GPG private key, converted to base64 binary .gpg (not armored .asc)
# #
- name: '🪪 GPG Import Signing Key w/ Passphrase'
if: env.GPG_KEY_BASE64 != '' && env.GPG_KEY_PASSPHRASE != ''
run: |
echo "$GPG_KEY_BASE64" | base64 -di > /tmp/signing-key.gpg
echo "$GPG_KEY_PASSPHRASE" | gpg --pinentry-mode loopback --passphrase-fd 0 --import /tmp/signing-key.gpg
(echo "$GPG_KEY_PASSPHRASE"; echo; echo) | gpg --command-fd 0 --pinentry-mode loopback --change-passphrase $(gpg --list-secret-keys --with-colons 2> /dev/null | grep '^sec:' | cut --delimiter ':' --fields 5 | tail -n 1)
# #
# GPG Checksum Stable
# #
- name: '🆔 Checksum Stable'
run: |
# windows
file_1_example="package.json"
file_2_example="package-lock.json"
# get sha1 and sha256 for .json
find . -maxdepth 1 \( -name '*.json' -o -name '*.gz' \) -printf '%P\n' | xargs -r sha1sum | gpg --digest-algo sha256 --clearsign > sha1sum.txt.asc
find . -maxdepth 1 \( -name '*.json' -o -name '*.gz' \) -printf '%P\n' | xargs -r sha256sum | gpg --digest-algo sha256 --clearsign > sha256sum.txt.asc
# SHA1SUM
sha1sum_file_1="$(shasum --algorithm 1 ${file_1_example} | awk '{ print $1 }')"
echo "SHA1SUM_FILE_1=${sha1sum_file_1}" >> $GITHUB_ENV
sha1sum_file_2="$(shasum --algorithm 1 ${file_2_example} | awk '{ print $1 }')"
echo "SHA1SUM_FILE_2=${sha1sum_file_2}" >> $GITHUB_ENV
# SHA256SUM
sha256sum_file_1="$(shasum --algorithm 256 ${file_1_example} | awk '{ print $1 }')"
echo "SHA256SUM_FILE_1=${sha256sum_file_1}" >> $GITHUB_ENV
sha256sum_file_2="$(shasum --algorithm 256 ${file_2_example} | awk '{ print $1 }')"
echo "SHA256SUM_FILE_2=${sha256sum_file_2}" >> $GITHUB_ENV
# no longer needed, replaced by find . command
# shasum --algorithm 256 ${file_file_1} > SHA256SUMS.txt
echo "FILE_1_EXAMPLE=${file_1_example}" >> $GITHUB_ENV
echo "FILE_2_EXAMPLE=${file_2_example}" >> $GITHUB_ENV
# generate sha256sum.sig from sha256sum.txt.asc file
gpg --batch --yes --quiet --armor --detach-sig --sign --output sha256sum.sig sha256sum.txt.asc
# #
# GPG Verbose Print Results
# #
- name: '⚙️ Verbose Print Results'
run: |
echo ""
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo -e " 🌲 Tree"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
tree -I node_modules
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo -e " 📄 sha256sum.txt.asc"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
cat sha256sum.txt.asc
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo -e " 📄 sha1sum.txt.asc"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
cat sha1sum.txt.asc
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo -e " 📄 sha256sum.sig"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
cat sha256sum.sig
echo ""
echo ""
echo ""
echo "Running command gpg --verify sha256sum.sig sha256sum.txt.asc"
echo ""
gpg --verify sha256sum.sig sha256sum.txt.asc
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo -e " 🔑 List GPG Keys"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
gpg --list-keys --keyid-format=long --fingerprint --with-fingerprint
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo ""
# #
# GPG Checksum Print
# #
- name: '🆔 Checksum Print'
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Checksums"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo " ${{ env.FILE_1_EXAMPLE }} ${{ env.SHA256SUM_FILE_1 }}"
echo " ${{ env.FILE_2_EXAMPLE }} ${{ env.SHA256SUM_FILE_2 }}"
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""

View File

@@ -12,11 +12,16 @@
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
@@ -62,37 +67,25 @@ on:
type: string
# #
# Main Branch
# Branch
#
# main branch re-recreate
# select branch to clean
# you must also run the workflow from that branch
# #
BRANCH_MAIN:
description: '🌳 Main Branch'
BRANCH:
description: '🌳 Branch'
required: true
default: 'main'
type: string
# #
# Deployment Environment Name
#
# this is the name of the deployment item
# #
DEPLOYMENT_ENV:
description: '📦 Deployment Environment'
required: true
default: 'orion'
type: string
# #
# environment variables
# #
env:
COMMIT_LABEL: ${{ github.event.inputs.COMMIT_LABEL || 'cleanup' }}
BRANCH_MAIN: ${{ github.event.inputs.BRANCH_MAIN || 'main' }}
DEPLOYMENT_ENV: ${{ github.event.inputs.DEPLOYMENT_ENV || 'orion' }}
BRANCH: ${{ github.event.inputs.BRANCH || 'main' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
@@ -106,43 +99,137 @@ jobs:
history-clean:
name: >-
🧹 History Clean
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 5
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 15
permissions:
contents: write
steps:
# #
# History Clean Set TImestamps
# #
- name: >-
🕛 Get Timestamp
id: task_history_clean_set_timestamp
run: |
echo "YEAR=$(date +'%Y')" >> $GITHUB_ENV
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
# #
# History Clean Checkout
# #
- name: >-
✅ Checkout
id: task_history_clean_gh_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# History Clean Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# History Clean Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# History Clean Git Identify
# #
- name: >-
🪪 Configure Git Identity
id: task_history_clean_git_ident
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
@@ -153,12 +240,11 @@ jobs:
- name: >-
📦 Commit Pre-commit
id: task_history_clean_commit_pre
run: |
now=$(date -u '+%m/%d/%Y %H:%M')
commit_label="${{ env.COMMIT_LABEL }}" >> $GITHUB_ENV
echo -e "$commit_label"
commit_message="\\\`️️🧹 $commit_label 🧹\\\` \\\`$now UTC\\\`" >> $GITHUB_ENV
commit_message="chore(maint): \\\`️️🧹 $commit_label 🧹\\\` \\\`$now UTC\\\`" >> $GITHUB_ENV
echo -e "$commit_message"
echo "COMMIT_MESSAGE=$(echo $commit_message)" >> $GITHUB_ENV
echo "NOW=$(echo $now)" >> $GITHUB_ENV
@@ -169,7 +255,6 @@ jobs:
- name: >-
📦 Commit Pre-commit Debug
id: task_history_clean_commit_debug
run: |
echo -e "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo -e " Printing Values"
@@ -184,7 +269,6 @@ jobs:
- name: >-
🧹 Clean Repo History
id: task_history_clean_history
run: |
# Create a new orphan branch
git checkout --orphan temp-branch
@@ -196,25 +280,25 @@ jobs:
git commit -m "${{ env.COMMIT_MESSAGE }}"
# Delete the old main branch
git branch -D ${{ env.BRANCH_MAIN }}
git branch -D ${{ env.BRANCH }}
# Rename the new orphan branch to main
git branch -m ${{ env.BRANCH_MAIN }}
git branch -m ${{ env.BRANCH }}
# Force push the new main branch to the remote repository
git push -f origin ${{ env.BRANCH_MAIN }}
git push -f origin ${{ env.BRANCH }}
# #
# History Clean References
# #
- name: >-
🗑️ Clean References
id: task_history_clean_references
🗑️ Garbage Collection (Aggressive)
run: |
# Remove remote-tracking references to deleted branches (optional)
git fetch origin --prune
git repack
git prune-packed
git reflog expire --expire=now --all
git gc --prune=now --aggressive
@@ -224,7 +308,6 @@ jobs:
- name: >-
📦 Commit Execute
id: task_history_clean_commit_execute
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: ${{ env.COMMIT_MESSAGE }}

View File

@@ -17,11 +17,16 @@
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
@@ -105,6 +110,7 @@ env:
{ "name": "Status Stale", "color": "928282", "description": "Has not had any activity in over 30 days" },
{ "name": "Type Bug", "color": "9a2c2c", "description": "Something isn't working" },
{ "name": "Type Dependency", "color": "243759", "description": "Item is associated to dependency" },
{ "name": "Type Lock Maintenance", "color": "FBCA04", "description": "Sync package-lock.json" },
{ "name": "Type Docs", "color": "0e588d", "description": "Improvements or modifications to docs" },
{ "name": "Type Feature", "color": "3c4e93", "description": "Feature request" },
{ "name": "Type Git Action", "color": "030406", "description": "GitHub Action / workflow" },
@@ -159,45 +165,128 @@ jobs:
job-labels-create:
name: >-
🎫 Labels Verify Existing
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
steps:
# #
# Ping Developer Set TImestamps
# Labels Create Checkout
# #
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Labels Create Job Information
# #
- name: >-
🕛 Get Timestamp
id: task_label_set_timestamp
run: |
echo "YEAR=$(date +'%Y')" >> $GITHUB_ENV
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# [ Create Labels ] Start
# Labels Create Start
# #
- name: >-
✅ Start
id: task_label_create_start
run: |
echo "Assigning labels and assignees"
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
# #
# [ Create Labels ] Checkout
# #
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
- name: >-
☑️ Checkout
id: task_label_create_checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# [ Create Labels ] Verify Existing Labels
@@ -246,8 +335,8 @@ jobs:
🏷️ Labels Assign
needs:
- job-labels-create
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
permissions:
contents: 'read'
@@ -255,6 +344,124 @@ jobs:
issues: 'write'
steps:
# #
# Labels Assign Checkout
# #
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Labels Assign Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Labels Assign Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Assign > Get Issue Title
#
@@ -984,8 +1191,8 @@ jobs:
🏷️ Labels Phrase Search
needs:
- job-labels-create
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
permissions:
contents: 'read'
@@ -1073,8 +1280,8 @@ jobs:
job-assign-assignees:
name: >-
✍️ Issue Assignees
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
needs: [ job-assign-labels ]
# disable

View File

@@ -16,11 +16,16 @@
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
@@ -53,6 +58,11 @@ on:
# #
env:
DISCORD_BOT_NAME: 'Europa'
DISCORD_BOT_AVATAR: 'https://i.imgur.com/UqwMom1.jpeg'
DISCORD_BOT_EMBED_AUTHOR_ICON: 'https://avatars.githubusercontent.com/u/200161462'
DISCORD_BOT_EMBED_THUMBNAIL: 'https://avatars.githubusercontent.com/u/200161462'
LABEL_CHECK_STATUS_FAILED: AC Failed
LABEL_CHECK_REVIEW_READY: AC Passed
LABEL_CHECK_CHANGES_REQ: AC Changes Required
@@ -64,6 +74,7 @@ env:
LABEL_TYPE_PR: Type Pull Request
LABEL_TYPE_DEPENDENCY: Type Dependency
LABEL_TYPE_GITACTION: Type Git Action
LABEL_TYPE_MAINTENANCE: Type Lock Maintenance
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
@@ -95,6 +106,7 @@ env:
{ "name": "Status Stale", "color": "928282", "description": "Has not had any activity in over 30 days" },
{ "name": "Type Bug", "color": "9a2c2c", "description": "Something isn't working" },
{ "name": "Type Dependency", "color": "243759", "description": "Item is associated to dependency" },
{ "name": "Type Lock Maintenance", "color": "FBCA04", "description": "Sync package-lock.json" },
{ "name": "Type Docs", "color": "0e588d", "description": "Improvements or modifications to docs" },
{ "name": "Type Feature", "color": "3c4e93", "description": "Feature request" },
{ "name": "Type Git Action", "color": "030406", "description": "GitHub Action / workflow" },
@@ -148,8 +160,8 @@ jobs:
job-pr-scan:
name: >-
🎫 Issues Autoscan
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
permissions:
contents: read
@@ -159,18 +171,122 @@ jobs:
steps:
# #
# PR Scan Set Env Variables
# PR Scan Checkout
# #
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# PR Scan Job Information
# #
- name: >-
🕛 Get Timestamp
id: task_prscan_set_timestamp
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# PR Scan Start
# #
- name: >-
✅ Start
run: |
echo "YEAR=$(date +'%Y')" >> $GITHUB_ENV
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# PR Scan Labels Verify Existing
@@ -184,7 +300,6 @@ jobs:
- name: >-
🎫 Labels Verify Existing
id: task_prscan_labels_verify
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
@@ -261,7 +376,6 @@ jobs:
- name: >-
#️⃣ Pull-Request ID Print
id: task_prscan_issue_num_get
run: |
echo '${{ steps.task_prscan_issue_num_set.outputs.result }}'
@@ -271,7 +385,6 @@ jobs:
- name: >-
☑️ Checkout
id: task_prscan_checkout
uses: actions/checkout@v4
if: |
( github.event_name == 'pull_request_target' ) || ( github.event_name == 'pull_request' ) || ( github.event_name == 'issue_comment' && contains( github.event.comment.html_url, '/pull/' ) && contains( github.event.comment.body, '/rescan' ) )
@@ -286,7 +399,6 @@ jobs:
- name: >-
⚙️ Setup Node
id: task_prscan_nodejs
uses: actions/setup-node@v4
# #
@@ -332,7 +444,6 @@ jobs:
- name: >-
📂 List Directories
id: task_prscan_dirs_list
run: |
ls
@@ -342,7 +453,6 @@ jobs:
- name: >-
☑️ Run Autocheck
id: task_prscan_run
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
@@ -554,7 +664,21 @@ jobs:
const type_dependency =
[
"dependabot/npm_and_yarn",
"renovate/github_actions"
"renovate/github_actions",
"renovate/testing-library",
"renovate/electron",
"renovate/aetherinox/noxenv",
"renovate/playwright",
"renovate/types",
"renovate/@types",
"renovate/eslint",
"renovate/stylistic",
"renovate/jimp",
"renovate/custom-electron-prompt",
"renovate/moment",
"renovate/semver",
"renovate/toasted",
"renovate/uuid"
];
const type_gitaction =
@@ -563,6 +687,11 @@ jobs:
"renovate/github_actions"
];
const type_maint =
[
"renovate/lock-file-maintenance"
];
const files_skipList =
[
".github",
@@ -919,11 +1048,14 @@ jobs:
const bGitaction = type_gitaction.some( s => s.includes( branch_ref ) || branch_ref.includes( s ) );
const bDependency = type_dependency.some( s => s.includes( branch_ref ) || branch_ref.includes( s ) );
const bMaintenance = type_maint.some( s => s.includes( branch_ref ) || branch_ref.includes( s ) );
if ( actor == "${{ env.BOT_NAME_DEPENDABOT }}" && bDependency || actor == "${{ env.BOT_NAME_RENOVATE }}" && bDependency )
labels.push( "${{ env.LABEL_TYPE_DEPENDENCY }}" );
else if ( actor == "${{ env.BOT_NAME_DEPENDABOT }}" && bGitaction || actor == "${{ env.BOT_NAME_RENOVATE }}" && bGitaction )
labels.push( "${{ env.LABEL_TYPE_GITACTION }}" );
else if ( actor == "${{ env.BOT_NAME_DEPENDABOT }}" && bMaintenance || actor == "${{ env.BOT_NAME_RENOVATE }}" && bMaintenance )
labels.push( "${{ env.LABEL_TYPE_MAINTENANCE }}" );
if ( ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_CHANGES_REQ }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_CHANGES_REQ }}" );
@@ -972,29 +1104,26 @@ jobs:
- name: >-
🕛 Get Weekly Commit List
id: task_prscan_set_weekly_commit_list
run: |
echo 'WEEKLY_COMMITS<<EOF' >> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV
echo 'EOF' >> $GITHUB_ENV
# #
# Autoscan Notify Github Success
# #
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_autocheck_notify_discord_success
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://cdn.pixabay.com/photo/2022/01/30/13/33/github-6980894_960_720.png'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## 🎫 Issues Scan ${{ job.status == 'success' && '✅' || '❌' }}
@@ -1036,16 +1165,15 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_autocheck_notify_discord_failure
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://cdn.pixabay.com/photo/2022/01/30/13/33/github-6980894_960_720.png'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## 🎫 Issues Scan ${{ job.status == 'success' && '✅' || '❌' }}

View File

@@ -27,11 +27,16 @@
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
@@ -56,8 +61,8 @@ run-name: '🎫 Issues Stale'
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
# schedule:
# - cron: "0 0 * * *"
# #
# environment variables
@@ -121,6 +126,7 @@ env:
{ "name": "Status Stale", "color": "928282", "description": "Has not had any activity in over 30 days" },
{ "name": "Type Bug", "color": "9a2c2c", "description": "Something isn't working" },
{ "name": "Type Dependency", "color": "243759", "description": "Item is associated to dependency" },
{ "name": "Type Lock Maintenance", "color": "FBCA04", "description": "Sync package-lock.json" },
{ "name": "Type Docs", "color": "0e588d", "description": "Improvements or modifications to docs" },
{ "name": "Type Feature", "color": "3c4e93", "description": "Feature request" },
{ "name": "Type Git Action", "color": "030406", "description": "GitHub Action / workflow" },
@@ -175,45 +181,128 @@ jobs:
job-labels-create:
name: >-
🎫 Labels Verify Existing
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
steps:
# #
# Issues (Stale) Labels Create Start
# Labels Create Checkout
# #
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Labels Create Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Labels Create Start
# #
- name: >-
✅ Start
id: task_labels_create_start
run: |
echo "Assigning labels and assignees"
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
# #
# Issues (Stale) Labels Create Set Env Variables
# #
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
- name: >-
🕛 Get Timestamp
id: task_labels_create_set_timestamp
run: |
echo "YEAR=$(date +'%Y')" >> $GITHUB_ENV
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
# #
# Issues (Stale) Labels Create Checkout
# #
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
- name: >-
☑️ Checkout
id: task_labels_create_checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Issues (Stale) Labels Create Verify Existing
@@ -227,7 +316,6 @@ jobs:
- name: >-
🎫 Labels Verify Existing
id: task_labels_create_verify
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
@@ -273,31 +361,137 @@ jobs:
name: >-
🎫 Labels Assign Missing
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 4
needs: job-labels-create
steps:
# #
# Issues (Stale) Labels Assign Missing Checkout
# Labels Assign Checkout
# #
- name: >-
☑️ Checkout
id: task_issues_nolabel_prepare
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Issues (Stale) Labels Assign Missing Check
# Labels Assign Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Labels Assign Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Labels Assign Check
#
# Check if repo has labels to use
# #
- name: >-
🎫 Labels Check
id: task_issues_nolabel_run
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
@@ -767,8 +961,8 @@ jobs:
job-issues-stale:
name: >-
💤 Scan Check Stale
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
needs:
- job-labels-create
@@ -780,12 +974,11 @@ jobs:
steps:
# #
# Stale Check Condition
# Labels Stale Check
# #
- name: >-
💤 Stale Check Condition
id: task_issues_inactive_stale
uses: actions/stale@v9
with:
repo-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
@@ -812,8 +1005,8 @@ jobs:
job-issues-lock:
name: >-
🔒 Scan Lock Inactive
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
needs:
- job-labels-create
@@ -821,12 +1014,11 @@ jobs:
steps:
# #
# Inactive Lock
# Labels Inactive Lock
# #
- name: >-
🔒 Inactive Lock
id: task_issues_inactive_lock
uses: dessant/lock-threads@v5
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}

View File

@@ -13,11 +13,16 @@
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
@@ -87,6 +92,7 @@ env:
{ "name": "Status Stale", "color": "928282", "description": "Has not had any activity in over 30 days" },
{ "name": "Type Bug", "color": "9a2c2c", "description": "Something isn't working" },
{ "name": "Type Dependency", "color": "243759", "description": "Item is associated to dependency" },
{ "name": "Type Lock Maintenance", "color": "FBCA04", "description": "Sync package-lock.json" },
{ "name": "Type Docs", "color": "0e588d", "description": "Improvements or modifications to docs" },
{ "name": "Type Feature", "color": "3c4e93", "description": "Feature request" },
{ "name": "Type Git Action", "color": "030406", "description": "GitHub Action / workflow" },
@@ -140,8 +146,8 @@ jobs:
issues-labels-clean:
name: >-
🧹 Labels Clean
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 3
permissions:
contents: 'read'
@@ -150,47 +156,129 @@ jobs:
steps:
# #
# Labels Start
# Labels Clean Checkout
# #
- name: >-
✅ Start
id: task_label_remove_start
run: |
echo "Starting workflow"
# #
# Labels Set Env Variables
# #
- name: >-
🕛 Get Timestamp
id: task_label_set_timestamp
run: |
echo "YEAR=$(date +'%Y')" >> $GITHUB_ENV
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
# #
# Labels Checkout
# #
- name: >-
☑️ Checkout
id: task_label_remove_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Labels Start
# Labels Clean Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Labels Clean Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Labels Clean Delete Existing Labels
# #
- name: >-
🏷️ Delete Existing Labels
id: task_label_remove_run
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
@@ -240,12 +328,11 @@ jobs:
return result
# #
# Labels Get Weekly Commits
# Labels Clean Get Weekly Commits
# #
- name: >-
🕛 Get Weekly Commit List
id: task_label_set_weekly_commit_list
run: |
echo 'WEEKLY_COMMITS<<EOF' >> $GITHUB_ENV
git log --format="[\`%h\`](${{ github.server_url }}/${{ github.repository }}/commit/%H) %s - %an" --since=7.days >> $GITHUB_ENV

View File

@@ -10,11 +10,16 @@
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
@@ -38,13 +43,71 @@ run-name: '🎫 Labels Create'
# #
on:
# #
# Trigger > Workflow Dispatch
# #
workflow_dispatch:
inputs:
# #
# Discord Bot Name
#
# The discord bot name
# #
DISCORD_BOT_NAME:
description: '🤖 Bot Name'
required: true
default: 'Europa'
type: string
# #
# Discord Bot Avatar
#
# The discord bot avatar to show; let's use some weird picture
# #
DISCORD_BOT_AVATAR:
description: '🤖 Avatar URL'
required: true
default: 'https://i.imgur.com/UqwMom1.jpeg'
type: string
# #
# Discord Bot Author Icon URL
#
# A small picture shown to the top-right of each post
# #
DISCORD_BOT_EMBED_AUTHOR_ICON:
description: '🤖 Embed Author Icon'
required: true
default: 'https://avatars.githubusercontent.com/u/200161462'
type: string
# #
# Discord Bot Thumbnail URL
#
# A small picture shown to the top-right of each post
# #
DISCORD_BOT_EMBED_THUMBNAIL:
description: '🤖 Embed Thumbnail URL'
required: true
default: 'https://avatars.githubusercontent.com/u/200161462'
type: string
# #
# environment variables
# #
env:
DISCORD_BOT_NAME: ${{ github.event.inputs.DISCORD_BOT_NAME || 'Europa' }}
DISCORD_BOT_AVATAR: ${{ github.event.inputs.DISCORD_BOT_AVATAR || 'https://i.imgur.com/UqwMom1.jpeg' }}
DISCORD_BOT_EMBED_AUTHOR_ICON: ${{ github.event.inputs.DISCORD_BOT_EMBED_AUTHOR_ICON || 'https://avatars.githubusercontent.com/u/200161462' }}
DISCORD_BOT_EMBED_THUMBNAIL: ${{ github.event.inputs.DISCORD_BOT_EMBED_THUMBNAIL || 'https://avatars.githubusercontent.com/u/200161462' }}
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
@@ -75,6 +138,7 @@ env:
{ "name": "Status Stale", "color": "928282", "description": "Has not had any activity in over 30 days" },
{ "name": "Type Bug", "color": "9a2c2c", "description": "Something isn't working" },
{ "name": "Type Dependency", "color": "243759", "description": "Item is associated to dependency" },
{ "name": "Type Lock Maintenance", "color": "FBCA04", "description": "Sync package-lock.json" },
{ "name": "Type Docs", "color": "0e588d", "description": "Improvements or modifications to docs" },
{ "name": "Type Feature", "color": "3c4e93", "description": "Feature request" },
{ "name": "Type Git Action", "color": "030406", "description": "GitHub Action / workflow" },
@@ -129,8 +193,8 @@ jobs:
issues-labels-create:
name: >-
🎫 Labels Create
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 3
permissions:
contents: 'read'
@@ -139,42 +203,125 @@ jobs:
steps:
# #
# [ Create Labels ] Start
# Labels Create Checkout
# #
- name: >-
✅ Start
id: task_label_create_start
run: |
echo "Assigning labels and assignees"
# #
# Create Labels Set Env Variables
# #
- name: >-
🕛 Get Timestamp
id: task_label_set_timestamp
run: |
echo "YEAR=$(date +'%Y')" >> $GITHUB_ENV
echo "NOW=$(date +'%m-%d-%Y %H:%M:%S')" >> $GITHUB_ENV
echo "NOW_SHORT=$(date +'%m-%d-%Y')" >> $GITHUB_ENV
echo "NOW_LONG=$(date +'%m-%d-%Y %H:%M')" >> $GITHUB_ENV
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
# #
# [ Create Labels ] Checkout
# #
- name: >-
☑️ Checkout
id: task_label_create_checkout
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# [ Create Labels ] Verify Existing Labels
# Labels Create Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Labels Create Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Labels Create Verify Existing Labels
# #
- name: >-
@@ -217,7 +364,7 @@ jobs:
return result
# #
# Cleanup Get Weekly Commits
# Labels Create Get Weekly Commits
# #
- name: >-
@@ -234,16 +381,15 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_label_notify_discord_success
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## 🎫 Labels Create ${{ job.status == 'success' && '✅' || '❌' }}
@@ -262,7 +408,7 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}
# #
# Cleanup Notify Github Failure
@@ -270,16 +416,15 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_label_notify_discord_failure
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
username: ${{ env.DISCORD_BOT_NAME }}
avatar-url: ${{ env.DISCORD_BOT_AVATAR }}
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS }}
embed-title: "⚙️ ${{ github.workflow_ref }}"
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-thumbnail-url: ${{ env.DISCORD_BOT_EMBED_THUMBNAIL }}
embed-description: |
## 🎫 Labels Create ${{ job.status == 'success' && '✅' || '❌' }}
@@ -296,5 +441,4 @@ jobs:
embed-timestamp: "${{ env.NOW_LONG }}"
embed-author-name: "${{ github.repository_owner }}"
embed-author-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-author-icon-url: "https://avatars.githubusercontent.com/u/200161462"
embed-author-icon-url: ${{ env.DISCORD_BOT_EMBED_AUTHOR_ICON }}

323
.github/workflows/ping-developer.yml vendored Normal file
View File

@@ -0,0 +1,323 @@
# #
# @type github workflow
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage pings the developer when an issue comment is made
#
# @secrets secrets.SELF_TOKEN self github personal access token (fine-grained)
# secrets.SELF_TOKEN_CL self github personal access token (classic)
# secrets.NPM_TOKEN self npmjs access token
# secrets.PYPI_API_TOKEN self Pypi API token (production site) - https://pypi.org/
# secrets.PYPI_API_TEST_TOKEN self Pypi API token (test site) - https://test.pypi.org/
# secrets.SELF_DOCKERHUB_TOKEN self Dockerhub token
# secrets.CODECOV_TOKEN codecov upload token for nodejs projects
# secrets.MAXMIND_GELITE_TOKEN maxmind API token
# secrets.CF_ACCOUNT_ID cloudflare account id
# secrets.CF_ACCOUNT_TOKEN cloudflare account token
# secrets.ORG_TOKEN org github personal access token (fine-grained)
# secrets.ORG_TOKEN_CL org github personal access token (classic)
# secrets.ORG_DOCKERHUB_TOKEN org dockerhub secret
# secrets.ORG_GITEA_TOKEN org gitea personal access token (classic) with package:write permission
# secrets.BOT_GPG_KEY_ASC bot gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.BOT_GPG_KEY_B64 bot gpg private key (binary) converted to base64
# secrets.BOT_GPG_PASSPHRASE bot gpg private key passphrase
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_RELEASES discord webhook to report release notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_WORKFLOWS discord webhook to report workflow notifications from github to discord
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_UPDATES discord webhook to report activity notifications from github to discord
#
# @local these workflows can be tested locally through the use of `act`
# https://github.com/nektos/act
# Extract act to folder
# Add system env var with path to act.exe
# Run the commands:
# git pull https://github.com/username/repo
# act -W .github/workflows/ping-developer.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/ping-developer.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: '💬 Ping Developer'
run-name: '💬 Ping Developer'
# #
# triggers
# #
on:
issue_comment:
types: [created]
# #
# environment variables
# #
env:
DEPLOYMENT_ENV: ${{ github.event.inputs.DEPLOYMENT_ENV || 'orion' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# jobs
#
# env not available for job.if
# #
jobs:
deploy:
name: >-
💬 Issue Accept
runs-on: ubuntu-latest
# runs-on: apollo-x64
timeout-minutes: 5
if: |
contains(github.event.comment.body, '/ping')
steps:
# #
# Ping Checkout
# #
- name: '☑️ Checkout'
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Ping Job Information
# #
- name: >-
🔄 Load Job
uses: qoomon/actions--context@v4
id: 'context'
# #
# Ping Start
# #
- name: >-
✅ Start
run: |
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo " Starting Job ${{ steps.context.outputs.job_name }}"
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
YEAR="$(date +'%Y')"
echo "YEAR=${YEAR}" >> $GITHUB_ENV
NOW="$(date +'%m-%d-%Y %H:%M:%S')" # 02-25-2025 12:49:48
echo "NOW=${NOW}" >> $GITHUB_ENV
NOW_SHORT="$(date +'%m-%d-%Y')" # 02-25-2025
echo "NOW_SHORT=${NOW_SHORT}" >> $GITHUB_ENV
NOW_LONG="$(date +'%m-%d-%Y %H:%M')" # 02-25-2025 12:49
echo "NOW_LONG=${NOW_LONG}" >> $GITHUB_ENV
NOW_DOCKER="$(date +'%Y%m%d')" # 20250225
echo "NOW_DOCKER=${NOW_DOCKER}" >> $GITHUB_ENV
NOW_DOCKER_TS="$(date -u +'%FT%T.%3NZ')" # 2025-02-25T12:50:11.569Z
echo "NOW_DOCKER_TS=${NOW_DOCKER_TS}" >> $GITHUB_ENV
SHA1="$(git rev-parse HEAD)" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1=${SHA1}" >> $GITHUB_ENV
SHA1_GH="$(echo ${GITHUB_SHA})" # 71fad013cfce9116ec62779e4a7e627fe4c33627
echo "SHA1_GH=${SHA1_GH}" >> $GITHUB_ENV
PKG_VER_1DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -d '.' -f1-1)" # 3.22 > 3
echo "PKG_VER_1DIGIT=${PKG_VER_1DIGIT}" >> $GITHUB_ENV
PKG_VER_2DIGIT="$(echo ${{ env.IMAGE_VERSION }} | cut -f2 -d ":" | cut -c1-3)" # 3.22 > 3.2
echo "PKG_VER_2DIGIT=${PKG_VER_2DIGIT}" >> $GITHUB_ENV
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
sudo apt -qq update
sudo apt -qq install tree
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
echo " Runner .............. ${{ runner.name }}"
echo " Workflow ............ ${{ github.workflow }} (#${{ github.workflow_ref }})"
echo " Run Number .......... ${{ github.run_number }}"
echo " Ref ................. ${{ github.ref }}"
echo " Ref Name ............ ${{ github.ref_name }}"
echo " Event Name .......... ${{ github.event_name }}"
echo " Repo ................ ${{ github.repository }}"
echo " Repo Owner .......... ${{ github.repository_owner }}"
echo " Run ID .............. https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo " Triggered By ........ ${{ github.actor }}"
echo " SHA 1 (GITHUB_SHA) .. ${GITHUB_SHA}"
echo " SHA 2 (github.sha) .. ${{ github.sha }}"
echo " SHA 3 (env.SHA1) .... ${SHA1}"
echo " SHA 4 (env.SHA1_GH) . ${SHA1_GH}"
echo " Workspace ........... ${{ github.workspace }}"
echo " PWD ................. ${PWD}"
echo " Job Name ............ ${{ steps.context.outputs.job_name }}"
echo " Job ID .............. ${{ steps.context.outputs.job_id }}"
echo " Job URL ............. ${{ steps.context.outputs.job_url }}"
echo " Run ID .............. ${{ steps.context.outputs.run_id }}"
echo " Run Attempt ......... ${{ steps.context.outputs.run_attempt }}"
echo " Run Number .......... ${{ steps.context.outputs.run_number }}"
echo " Run URL ............. ${{ steps.context.outputs.run_url }}"
echo " Run Env ............. ${{ steps.context.outputs.environment }}"
echo " Run Env URL ......... ${{ steps.context.outputs.environment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Deployment URL .. ${{ steps.context.outputs.deployment_url }}"
echo " Run Deployment ...... ${{ steps.context.outputs.deployment_id }}"
echo " Run Runner Name ..... ${{ steps.context.outputs.runner_name }}"
echo " Run Runner ID ....... ${{ steps.context.outputs.runner_id }}"
echo " Year ................ ${YEAR}"
echo " Now ................. ${NOW}"
echo " Now (Short) ......... ${NOW_SHORT}"
echo " Now (Long) .......... ${NOW_LONG}"
echo " Now (Docker) ........ ${NOW_DOCKER}"
echo " Now (Docker TS) ..... ${NOW_DOCKER_TS}"
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
tree -I node_modules -I .git
echo ""
echo ""
echo "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo ""
echo ""
# #
# Ping Developer Send Mail
#
# Add Label to accepted PR
#
# port 465
# server_port: 465
# secure: true
# ignore_cert: false
#
# port 587
# server_port: 587
# secure: false
# #
- name: >-
📨 Send mail
id: task_ping_developer_mail
uses: dawidd6/action-send-mail@v5
with:
server_address: ${{ secrets.EMAIL_SMTP }}
server_port: 465
secure: true
username: ${{ secrets.EMAIL_FROM }}
password: ${{ secrets.EMAIL_KEY }}
subject: "Github: Ping notification from ${{ github.repository }}"
to: ${{ secrets.EMAIL_TO }}
from: ${{ secrets.EMAIL_FROM }}
html_body: |
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Title</title>
<style>
body {
background: url('https://images.unsplash.com/photo-1541422348463-9bc715520974?fm=jpg&q=60&w=3000&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8M3x8ZGFyayUyMG1vdW50YWlufGVufDB8fDB8fHww');
font-size:9pt;
margin:0;
padding:0;
}
.background-overlay {
background-color: #1111119f;
}
.background-header {
background: url('https://process.fs.teachablecdn.com/ADNupMnWyR7kCWRvm76Laz/resize=width:705/https://cdn.filestackcontent.com/MipxnobQRRS5h7raz9aM');
background-size: cover;
background-size: 100%;
background-color:#1b1b1b;
padding:5px;
height:100px;
}
</style>
</head>
<body>
<div class="background-overlay">
<center>
<div class="background-header">
<a href="https://github.com/${{ github.repository }}"><img style="height:80px;padding-top:10px;" src="https://cdn0.iconfinder.com/data/icons/shift-logotypes/32/Github-512.png"></a>
</div>
</center>
<div style="font-size:9pt;padding: 20px;color:#FFF;">
<h3><span style="font-size:9pt;color:#cc6613;">[Github]</span> <span style="font-size:9pt;color:#FFF;">Dear ${{github.repository_owner}},</span></h3>
<p style="font-size:9pt;color:#FFF;"><br />You have received a ping notification from <a href="https://github.com/${{ github.repository }}">${{ github.repository }}</a> by <a href="https://github.com/${{ github.event.comment.user.login }}">${{ github.event.comment.user.login }}</a>.</p>
<br>
<br>
<center>
<table cellspacing="0" cellpadding="0" width="40%" class="center">
<tbody>
<tr>
<td
style="font-size:9pt;background-color:#8a2138;color:#FFF;padding:6px;padding-left:10px;"><b>Repository</b></td>
<td style="font-size:9pt;padding-left:5px;color:#b3b3b3;background-color:#1b1b1b;padding-left:10px;">${{ github.repository }}</td>
</tr>
<tr>
<td
style="font-size:9pt;background-color:#8a2138;color:#FFF;padding:6px;padding-left:10px;"><b>Date</b></td>
<td style="font-size:9pt;padding-left:5px;color:#b3b3b3;background-color:#0f0f0f;padding-left:10px;">${{ env.NOW }}</td>
</tr>
<tr>
<td
style="font-size:9pt;background-color:#8a2138;color:#FFF;padding:6px;padding-left:10px;"><b>Commenter</b></td>
<td style="font-size:9pt;padding-left:5px;color:#b3b3b3;background-color:#1b1b1b;padding-left:10px;">${{ github.event.comment.user.login }}</td>
</tr>
<tr>
<td
style="font-size:9pt;background-color:#8a2138;color:#FFF;padding:6px;padding-left:10px;"><b>Issue #</b></td>
<td style="font-size:9pt;padding-left:5px;color:#b3b3b3;background-color:#0f0f0f;padding-left:10px;">${{ github.event.issue.number }}</td>
</tr>
<tr>
<td
style="font-size:9pt;background-color:#8a2138;color:#FFF;padding:6px;padding-left:10px;"><b>Action</b></td>
<td style="font-size:9pt;padding-left:5px;color:#b3b3b3;background-color:#1b1b1b;padding-left:10px;">Notification</td>
</tr>
</tbody>
</table>
</center>
<br><br>
<center>
<div style="font-family:Consolas;">
<textarea readonly=true style="font-size:9pt;width:60%;background-color:#363636;color:#FFF;padding:15px;border:1px solid #5a5a5a;" id="w3review" name="w3review" rows="20" cols="100">
${{ github.event.comment.body }}
</textarea>
</div>
</center>
<p>&nbsp;</p>
<p style="color:#FFF;"><br /> ~ Github
</p>
</div>
<br /><br />
<div style="background-color:#1b1b1b;padding:5px;line-height:70px;height:70px;text-align:center;">
<span style="color:#FFF;font-size:8pt;">Copyright &copy; ${{ env.YEAR }}</span>
</div>
</div>
</body>
</html>
ignore_cert: true
convert_markdown: true
priority: normal

View File

@@ -53,8 +53,8 @@ on:
type: string
# #
# ENABLE: the changelog generated in releases tab will only display single commits.
# DISABLE: the changelog shows pull requests completed based on their labels
# true the changelog generated in releases tab will only display single commits.
# false the changelog shows pull requests completed based on their labels
# #
CHANGELOG_MODE_COMMIT:
@@ -64,8 +64,8 @@ on:
type: boolean
# #
# ENABLE: Will show all types of commits, including uncategorized
# DISABLE: WIll only show actions that have been categorized using the format
# true Will show all types of commits, including uncategorized
# false WIll only show actions that have been categorized using the format
# type(scope): description
# type: description
# #
@@ -256,7 +256,6 @@ jobs:
# #
- name: '🪪 Test Next Job Version'
id: task_release_debug_print_ver
run: |
echo "VERSION: ${{ env.PACKAGE_VERSION }}"
@@ -265,7 +264,6 @@ jobs:
# #
- name: '🪪 NPM Install & Lint'
id: task_release_npm_install
working-directory: ./tvapp2
run: |
npm ci
@@ -278,7 +276,6 @@ jobs:
# #
- name: '🪪 Generate IDs'
id: task_release_npm_env_generate
working-directory: ./tvapp2
run: |
npm run root:generate
@@ -300,7 +297,6 @@ jobs:
# #
- name: '🪪 .ENV Read'
id: task_dotenv_debug_print
run: |
echo "GUID: ${{ steps.task_release_dotenv_get.outputs.GUID }}"
echo "UUID: ${{ steps.task_release_dotenv_get.outputs.UUID }}"
@@ -310,7 +306,6 @@ jobs:
# #
- name: '🔨 Build Stable ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip'
id: task_release_build_st
if: |
startsWith( inputs.RC_RELEASE, false ) ||
startsWith( env.RC_RELEASE, false )
@@ -320,6 +315,7 @@ jobs:
echo Building STABLE Package ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-docker-compose.zip
zip -r ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-docker-compose.zip docker-compose.yml README.md LICENSE
ls
env:
NODE_AUTH_TOKEN: ${{ secrets.ADMINSERV_TOKEN_CL }}
@@ -366,7 +362,6 @@ jobs:
# #
- name: '🔖 Tag Confirm ${{ env.PACKAGE_VERSION }}'
id: task_release_tag_get
run: |
echo "Tag already present: ${{ env.TAG_EXISTS }}"
echo "Tag already present: ${{ steps.task_release_tag_create.outputs.tag_exists }}"
@@ -378,7 +373,6 @@ jobs:
# #
- name: '🪪 GPG Import Signing Key W/o Passphrase'
id: task_release_gpg_import_nopass
if: env.GPG_KEY_BASE64 != '' && env.GPG_KEY_PASSPHRASE == ''
run: |
echo $GPG_KEY_BASE64 | base64 -di | gpg --import
@@ -390,7 +384,6 @@ jobs:
# #
- name: '🪪 GPG Import Signing Key w/ Passphrase'
id: task_release_gpg_import_withpass
if: env.GPG_KEY_BASE64 != '' && env.GPG_KEY_PASSPHRASE != ''
run: |
echo "$GPG_KEY_BASE64" | base64 -di > /tmp/signing-key.gpg
@@ -429,6 +422,9 @@ jobs:
sha256sum_compose="$(shasum --algorithm 256 ${filename_compose_zip} | awk '{ print $1 }')"
echo "FILE_COMPOSE_ZIP=${filename_compose_zip}" >> $GITHUB_ENV
gpg --batch --yes --quiet --armor --detach-sig --sign --output sha256sum.sig sha256sum.txt.asc
gpg --batch --yes --quiet --armor --detach-sig --sign --output sha1sum.sig sha1sum.txt.asc
# #
# Release Checksum Release Candidate
# #
@@ -461,21 +457,23 @@ jobs:
sha256_compose="$(shasum --algorithm 256 ${filename_compose_zip} | awk '{ print $1 }')"
echo "FILE_COMPOSE_ZIP=${filename_compose_zip}" >> $GITHUB_ENV
gpg --batch --yes --quiet --armor --detach-sig --sign --output sha256sum.sig sha256sum.txt.asc
gpg --batch --yes --quiet --armor --detach-sig --sign --output sha1sum.sig sha1sum.txt.asc
# #
# Release Checksum Print
# #
- name: '🆔 Checksum Print'
id: task_release_checksum_st_get
run: |
echo "${{ env.SHA256SUM }}"
echo SHA1SUM ............... ${{ env.SHA1SUM }}
echo SHA256SUM ............. ${{ env.SHA256SUM }}
# #
# Release Contributor Images
# #
- name: '🥸 Contributors Generate'
id: task_release_contribs_generate
uses: jaywcjlove/github-action-contributors@main
with:
filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\])
@@ -485,7 +483,7 @@ jobs:
# #
# Release Changelog Generate Tags
#
# generates a changelog from the github api. requires a PREVIOUS_TAG in order to figure
# generates a changelog from the github api. requires a TAG_LAST in order to figure
# out the changes made between the two versions.
#
# outputs:
@@ -493,7 +491,6 @@ jobs:
# #
- name: '📝 Changelog Pre Setup (Categorized Commits)'
id: task_release_changelog_categorized_sha_set
run: |
echo "TAG_LAST=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
echo "COMMIT_LAST=$(git rev-parse HEAD)" >> $GITHUB_ENV
@@ -685,6 +682,8 @@ jobs:
${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip
sha1sum.txt.asc
sha256sum.txt.asc
sha256sum.sig
sha1sum.sig
prerelease: false
body: |
${{ steps.task_release_changelog_categorized.outputs.changelog }}
@@ -721,6 +720,8 @@ jobs:
${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.RC_VERSION }}.zip
sha1sum.txt.asc
sha256sum.txt.asc
sha256sum.sig
sha1sum.sig
prerelease: false
body: |
> [!WARNING]

1
.gitignore vendored
View File

@@ -3,6 +3,7 @@
# #
node_modules/
tvapp2/node_modules/
jspm_packages/
# #

View File

@@ -45,7 +45,7 @@
# #
ARG ARCH=amd64
ARG ALPINE_VERSION=3.21
ARG ALPINE_VERSION=3.22
FROM --platform=linux/${ARCH} ghcr.io/aetherinox/alpine-base:${ALPINE_VERSION}
# #
@@ -53,11 +53,12 @@ FROM --platform=linux/${ARCH} ghcr.io/aetherinox/alpine-base:${ALPINE_VERSION}
# #
ARG ARCH=amd64
ARG ALPINE_VERSION=3.21
ARG ALPINE_VERSION=3.22
ARG BUILDDATE
ARG VERSION
ARG RELEASE
ARG GIT_SHA1=0000000000000000000000000000000000000000
ARG REGISTRY=local
# #
# Set Labels
@@ -74,29 +75,31 @@ LABEL org.opencontainers.image.repo.3="https://github.com/aetherinox/docker-base
LABEL org.opencontainers.image.documentation="https://thebinaryninja.github.io/tvapp2"
LABEL org.opencontainers.image.url="https://github.com/thebinaryninja/tvapp2/pkgs/container/tvapp2"
LABEL org.opencontainers.image.licenses="MIT"
LABEL org.opencontainers.image.architecture="${ARCH}"
LABEL org.opencontainers.image.architecture="${ARCH:-amd64}"
LABEL org.opencontainers.image.ref.name="main"
LABEL org.opencontainers.image.registry="local"
LABEL org.opencontainers.image.release="${RELEASE}"
LABEL org.opencontainers.image.registry="${REGISTRY:-local}"
LABEL org.opencontainers.image.release="${RELEASE:-stable}"
LABEL org.tvapp2.image.maintainers="Aetherinox, iFlip721, Optx"
LABEL org.tvapp2.image.build-version="Version:- ${VERSION} Date:- ${BUILDDATE}"
LABEL org.tvapp2.image.build-version-alpine="${ALPINE_VERSION}"
LABEL org.tvapp2.image.build-architecture="${ARCH}"
LABEL org.tvapp2.image.build-release="${RELEASE}"
LABEL org.tvapp2.image.build-sha1="${GIT_SHA1}"
LABEL org.tvapp2.image.build-version="Version:- ${VERSION} Date:- ${BUILDDATE:-3.21}"
LABEL org.tvapp2.image.build-version-alpine="${ALPINE_VERSION:-3.21}"
LABEL org.tvapp2.image.build-architecture="${ARCH:-amd64}"
LABEL org.tvapp2.image.build-release="${RELEASE:-stable}"
LABEL org.tvapp2.image.build-sha1="${GIT_SHA1:-0000000000000000000000000000000000000000}"
# #
# Set Env Var
# #
ENV NODE_VERSION=22.8.0
ENV NODE_VERSION=22.16.0
ENV YARN_VERSION=1.22.22
ENV RELEASE="${RELEASE}"
ENV NPM_VERSION=10.9.2
ENV RELEASE="${RELEASE:-stable}"
ENV DIR_BUILD=/usr/src/app
ENV DIR_RUN=/usr/bin/app
ENV URL_REPO="https://git.binaryninja.net/binaryninja/"
ENV WEB_IP="0.0.0.0"
ENV WEB_PORT=4124
ENV HDHR_PORT=6077
ENV WEB_ENCODING="deflate, br"
ENV WEB_PROXY_HEADER="x-forwarded-for"
ENV STREAM_QUALITY="hd"

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 BinaryNinja
Copyright (c) 2025-2026 BinaryNinja
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

205
README.md
View File

@@ -86,6 +86,7 @@
- [Labels](#labels-1)
- [Dynamic.yml](#dynamicyml-1)
- [Troubleshooting](#troubleshooting)
- [Run Error: `Warning: Step size 60 higher than possible maximum of 59`](#run-error-warning-step-size-60-higher-than-possible-maximum-of-59)
- [Run Error: `Error serving playlist: ENOENT: no such file or directory, open /usr/src/app/xmltv.1.xml`](#run-error-error-serving-playlist-enoent-no-such-file-or-directory-open-usrsrcappxmltv1xml)
- [Build Error: `s6-rc-compile: fatal: invalid /etc/s6-overlay/s6-rc.d/certsync/type: must be oneshot, longrun, or bundle`](#build-error-s6-rc-compile-fatal-invalid-etcs6-overlays6-rcdcertsynctype-must-be-oneshot-longrun-or-bundle)
- [Build Error: `unable to exec /etc/s6-overlay/s6-rc.d/init-envfile/run: Permission denied`](#build-error-unable-to-exec-etcs6-overlays6-rcdinit-envfilerun-permission-denied)
@@ -183,6 +184,7 @@ The following is a list of environment variables you can declare within your `do
| `WEB_FOLDER` | `www` | Internal container folder to keep TVApp2 web files in. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
| `WEB_ENCODING` | `deflate, br` | Defines the HTTP `Accept-Encoding` request and response header. This value specifies what content encoding the sender can understand<br /><br />Gzip compression can be enabled by specifying `'gzip, deflate, br'`, however, [it may break Jellyfin users](#build-error-err-27-jellyfinlivetvguideguidemanager-error-getting-programs-for-channel-xxxxxxxxxxxxxxx-source-2-systemxmlxmlexception--hexadecimal-value-0x1f-is-an-invalid-character-line-1-position-1). |
| `WEB_PROXY_HEADER` | `x-forwarded-for` | Defines the header to look for when finding a client's IP address. Used to get a client's IP when behind a reverse proxy or Cloudflare |
| `HDHR_PORT` | `6077` | HDHomeRun server default listening port |
| `URL_REPO` | `https://git.binaryninja.net/BinaryNinja/` | Determines where the data files will be downloaded from. Do not change this or you will be unable to get M3U and EPG data. |
| `FILE_URL` | `urls.txt` | Filename for `urls.txt` cache file |
| `FILE_M3U` | `playlist.m3u8` | Filename for M3U playlist file |
@@ -389,7 +391,7 @@ This repository offers two types of docker image; `stable` and `development`. Yo
| Build | Tags |
| ------------------------- | ----------------------------------------------------------------------------- |
| `Stable` | `🔖 tvapp2:latest` <br /> `🔖 tvapp2:1.5.2` <br /> `🔖 tvapp2:1.5` <br /> `🔖 tvapp2:1` |
| `Stable` | `🔖 tvapp2:latest` <br /> `🔖 tvapp2:1.5.4` <br /> `🔖 tvapp2:1.5` <br /> `🔖 tvapp2:1` |
| `Development` | `🔖 tvapp2:development` |
<br />
@@ -403,7 +405,7 @@ Prior to building the docker image, you **must** ensure the sections below are
<br />
If the listed tasks above are not performed, your docker container will throw the following errors when started:
If the listed tasks above are not performed, your docker container will throw the following errors when started:
- `Failed to open apk database: Permission denied`
- `s6-rc: warning: unable to start service init-adduser: command exited 127`
@@ -411,13 +413,14 @@ Prior to building the docker image, you **must** ensure the sections below are
- `/etc/s6-overlay/s6-rc.d/init-adduser/run: line 34: aetherxown: command not found`
- `/etc/s6-overlay/s6-rc.d/init-adduser/run: /usr/bin/aetherxown: cannot execute: required file not found`
<br />
<br />
#### LF over CRLF
You cannot utilize Windows' `Carriage Return Line Feed`. All files must be converted to Unix' `Line Feed`. This can be done with **[Visual Studio Code](https://code.visualstudio.com/)**. OR; you can run the Linux terminal command `🗔 dos2unix` to convert these files.
If you cloned the files from the official repository [🔆 gitea:binaryninja/tvapp2](https://git.binaryninja.net/binaryninja/tvapp2) and have not edited them, then you should not need to do this step.
For the branches **[docker/alpine-base](https://github.com/Aetherinox/docker-base-alpine/tree/docker/alpine-base)** and your main app image, you can use the following recursive commands:
<br />
@@ -434,20 +437,63 @@ If you cloned the files from the official repository [🔆 gitea:binaryninja/tva
```shell
# Change ALL files
find ./ -type f | grep -Ev '.git|*.jpg|*.jpeg|*.png' | xargs dos2unix --
find ./ -type f | grep -Ev 'docs|node_modules|.git|*.jpg|*.jpeg|*.png' | xargs dos2unix --
# Change run / binaries
find ./ -type f -name 'run' | xargs dos2unix --
find ./ -type f -name 'run' -print | xargs dos2unix --
```
<br />
#### Set `+x / 0755` Permissions
The files contained within this repo **MUST** have `chmod 755` / `+x` executable permissions.
For the branch **[docker/core](https://github.com/Aetherinox/docker-base-alpine/tree/docker/core)**, you can use the following commands:
```shell
find ./ -name 'run' -exec sudo chmod +x {} \;
dos2unix docker-images.v3
dos2unix aetherxown.v1
dos2unix package-install.v1
dos2unix with-contenv.v1
```
<br />
If you do not have dos2unix; you may use `sed:
```shell
sed -i 's/\r$//' /etc/s6-overlay/s6-rc.d/ci-service-check/file
```
<br />
You may pre-check if a file is using Windows CRLF or Linux LF by running the command `file <filename>` on the file:
```shell
$ file ./root//etc/s6-overlay/s6-rc.d/ci-service-check/type
./root//etc/s6-overlay/s6-rc.d/ci-service-check/type: ASCII text
```
<br />
You will get one of three messages listed below:
1. ASCII text, with CRLF, LF line terminators
2. ASCII text, with CRLF line terminators
3. ASCII text
<br />
If you get messages `1` or `2`, then you need to run `dos2unix` on the file; otherwise when you bring the container up, you will get errors.
<br />
<br />
#### Set `+x / 0755` Permissions
The files contained within this repo **MUST** have `chmod 755` / `+x` executable permissions. If you are using our Github workflow sample **[deploy-docker-github.yml](https://github.com/Aetherinox/docker-base-alpine/blob/workflows/samples/deploy-docker-github.yml)**, this is done automatically. If you are building the images manually; you need to do this. Ensure those files have the correct permissions prior to building the Alpine base docker image.
If you are building the **[docker/alpine-base](https://github.com/Aetherinox/docker-base-alpine/tree/docker/alpine-base)** or your main application images, you must ensure the files in those branches have the proper permissions. All of the executable files are named `run`:
```shell
find ./ -name 'run' -print -exec sudo chmod +x {} \;
```
<br />
@@ -466,11 +512,26 @@ sudo chmod +x ./root/etc/s6-overlay/s6-rc.d/init-adduser/run \
./root/etc/s6-overlay/s6-rc.d/init-samples/run \
./root/etc/s6-overlay/s6-rc.d/init-version-checks/run \
./root/etc/s6-overlay/s6-rc.d/svc-cron/run \
./root/etc/services.d/tvapp2/run
./root/etc/s6-overlay/s6-rc.d/svc-php-fpm/run \
./root/etc/s6-overlay/s6-rc.d/svc-nginx/run \
./root/etc/s6-overlay/s6-rc.d/init-php/run \
./root/etc/s6-overlay/s6-rc.d/init-nginx/run
```
<br />
For the branch **[docker/core](https://github.com/Aetherinox/docker-base-alpine/tree/docker/core)**, there are a few files to change. The ending version number may change, but the commands to change the permissions are as follows:
```shell
sudo chmod +x docker-images.v3 \
chmod +x aetherxown.v1 \
chmod +x package-install.v1 \
chmod +x with-contenv.v1
```
<br />
<br />
### Build Images
After completing the steps above; we will now build the [🔆 gitea:binaryninja/tvapp2](https://git.binaryninja.net/binaryninja/tvapp2) image.
@@ -481,7 +542,7 @@ Open the `📄 Dockerfile` and ensure you are pulling the correct Alpine base im
```dockerfile
ARG ARCH=amd64
FROM --platform=linux/${ARCH} ghcr.io/aetherinox/alpine-base:3.21
FROM --platform=linux/${ARCH} ghcr.io/aetherinox/alpine-base:3.22
```
<br />
@@ -510,7 +571,7 @@ All of the needed Docker files already exist in the repository. To get started,
mkdir tvapp2 && cd tvapp2
# to clone from our gitea website
git clone https://git.binaryninja.net/binarynina/tvapp2.git ./
git clone https://git.binaryninja.net/binaryninja/tvapp2.git ./
# to clone from our github website
git clone https://github.com/thebinaryninja/tvapp2.git ./
@@ -601,10 +662,11 @@ Creates the TVApp2 `amd64` docker image:
# Build tvapp2 amd64
docker buildx build \
--build-arg ARCH=amd64 \
--build-arg VERSION=1.5.2 \
--build-arg VERSION=1.5.4 \
--build-arg BUILDDATE=20260812 \
--build-arg RELEASE=stable \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.2 \
--build-arg ALPINE_VERSION=3.22 \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.4 \
--tag ghcr.io/thebinaryninja/tvapp2:1.5 \
--tag ghcr.io/thebinaryninja/tvapp2:1 \
--tag ghcr.io/thebinaryninja/tvapp2:latest \
@@ -631,10 +693,11 @@ Creates the TVApp2 `arm64` docker image:
# Build tvapp2 arm64
docker buildx build \
--build-arg ARCH=arm64 \
--build-arg VERSION=1.5.2 \
--build-arg VERSION=1.5.4 \
--build-arg BUILDDATE=20260812 \
--build-arg RELEASE=stable \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.2 \
--build-arg ALPINE_VERSION=3.22 \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.4 \
--tag ghcr.io/thebinaryninja/tvapp2:1.5 \
--tag ghcr.io/thebinaryninja/tvapp2:1 \
--tag ghcr.io/thebinaryninja/tvapp2:latest \
@@ -676,7 +739,7 @@ All of the needed Docker files already exist in the repository. To get started,
mkdir tvapp2 && cd tvapp2
# to clone from our gitea website
git clone https://git.binaryninja.net/binarynina/tvapp2.git ./
git clone https://git.binaryninja.net/binaryninja/tvapp2.git ./
# to clone from our github website
git clone https://github.com/thebinaryninja/tvapp2.git ./
@@ -755,7 +818,7 @@ You should see your name:
<br />
Next, in order to build the `amd64` and `arm64` images on the same machine; you must install **QEMU** using:
Next, in order to build the `amd64` and `arm64` images on the same machine; you must install **QEMU** which is an emulator. Open your terminal and run the following command:
```shell
docker run --privileged --rm tonistiigi/binfmt --install all
@@ -763,10 +826,20 @@ docker run --privileged --rm tonistiigi/binfmt --install all
<br />
If you are building these docker images using Github workflow, you will also need to use `QEMU` with the following action:
```yml
- name: '⚙️ Set up QEMU'
id: task_release_gh_qemu
uses: docker/setup-qemu-action@v3
```
<br />
Once the emulator is installed; we will now build two images. When building these two images; we will ensure the `--tag` value is different for each one, by adding the architecture to the end. This ensures we don't overwrite one image with the newer one. We need to have two seperate docker images with two different tags.
- `--tag ghcr.io/thebinaryninja/tvapp2:1.5.2-amd64`
- `--tag ghcr.io/thebinaryninja/tvapp2:1.5.2-arm64`
- `--tag ghcr.io/thebinaryninja/tvapp2:1.5.4-amd64`
- `--tag ghcr.io/thebinaryninja/tvapp2:1.5.4-arm64`
<br />
@@ -777,10 +850,10 @@ Once the emulator is installed; we will now build two images. When building thes
>
> | Registry | Tag |
> | --- | --- |
> | Dockerhub | `--tag thebinaryninja/tvapp2:1.5.2-amd64`<br>`--tag thebinaryninja/tvapp2:1.5.2-arm64` |
> | Github (GHCR) | `--tag ghcr.io/thebinaryninja/tvapp2:1.5.2-amd64`<br>`--tag ghcr.io/thebinaryninja/tvapp2:1.5.2-arm64` |
> | Registry v2 | `--tag registry.domain.lan/thebinaryninja/tvapp2:1.5.2-amd64`<br>`--tag registry.domain.lan/thebinaryninja/tvapp2:1.5.2-arm64` |
> | Gitea | `--tag git.binaryninja.net/binaryninja/tvapp2:1.5.2-amd64`<br>`--tag git.binaryninja.net/binaryninja/tvapp2:1.5.2-arm64` |
> | Dockerhub | `--tag thebinaryninja/tvapp2:1.5.4-amd64`<br>`--tag thebinaryninja/tvapp2:1.5.4-arm64` |
> | Github (GHCR) | `--tag ghcr.io/thebinaryninja/tvapp2:1.5.4-amd64`<br>`--tag ghcr.io/thebinaryninja/tvapp2:1.5.4-arm64` |
> | Registry v2 | `--tag registry.domain.lan/thebinaryninja/tvapp2:1.5.4-amd64`<br>`--tag registry.domain.lan/thebinaryninja/tvapp2:1.5.4-arm64` |
> | Gitea | `--tag git.binaryninja.net/binaryninja/tvapp2:1.5.4-amd64`<br>`--tag git.binaryninja.net/binaryninja/tvapp2:1.5.4-arm64` |
<br />
@@ -801,10 +874,11 @@ Creates the TVApp2 **Stable** release `amd64` docker image:
# Build Tvapp2 amd64 - (stable release)
docker buildx build \
--build-arg ARCH=amd64 \
--build-arg VERSION=1.5.2 \
--build-arg VERSION=1.5.4 \
--build-arg BUILDDATE=20260812 \
--build-arg RELEASE=stable \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.2-amd64 \
--build-arg ALPINE_VERSION=3.22 \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.4-amd64 \
--attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \
--output type=docker \
@@ -829,10 +903,11 @@ Creates the TVApp2 **Stable** release `arm64` docker image:
# Build Tvapp2 arm64 - (stable release)
docker buildx build \
--build-arg ARCH=arm64 \
--build-arg VERSION=1.5.2 \
--build-arg VERSION=1.5.4 \
--build-arg BUILDDATE=20260812 \
--build-arg RELEASE=stable \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.2-arm64 \
--build-arg ALPINE_VERSION=3.22 \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.4-arm64 \
--attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \
--output type=docker \
@@ -857,9 +932,10 @@ Creates the TVApp2 **Development** release `amd64` docker image:
# Build Tvapp2 amd64 - (development release)
docker buildx build \
--build-arg ARCH=amd64 \
--build-arg VERSION=1.5.2 \
--build-arg VERSION=1.5.4 \
--build-arg BUILDDATE=20260812 \
--build-arg RELEASE=development \
--build-arg ALPINE_VERSION=3.22 \
--tag ghcr.io/thebinaryninja/tvapp2:development-amd64 \
--attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \
@@ -885,9 +961,10 @@ Creates the TVApp2 **Development** release `arm64` docker image:
# Build Tvapp2 arm64 - (development release)
docker buildx build \
--build-arg ARCH=arm64 \
--build-arg VERSION=1.5.2 \
--build-arg VERSION=1.5.4 \
--build-arg BUILDDATE=20260812 \
--build-arg RELEASE=development \
--build-arg ALPINE_VERSION=3.22 \
--tag ghcr.io/thebinaryninja/tvapp2:development-arm64 \
--attest type=provenance,disabled=true \
--attest type=sbom,disabled=true \
@@ -907,8 +984,8 @@ docker buildx build \
After completing the `docker buildx` commands above; you should now have a few new images. Each image should have its own separate docker tags which do not conflict. If you decided to not build the **development** releases below; that is fine.
- `--tag ghcr.io/thebinaryninja/tvapp2:1.5.2-amd64`
- `--tag ghcr.io/thebinaryninja/tvapp2:1.5.2-arm64`
- `--tag ghcr.io/thebinaryninja/tvapp2:1.5.4-amd64`
- `--tag ghcr.io/thebinaryninja/tvapp2:1.5.4-arm64`
- `--tag ghcr.io/thebinaryninja/tvapp2:development-amd64`
- `--tag ghcr.io/thebinaryninja/tvapp2:development-arm64`
@@ -934,15 +1011,15 @@ You can also get the hash digests by running the commands:
<br />
```shell
$ docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.2-amd64
$ docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.4-amd64
Name: ghcr.io/thebinaryninja/tvapp2:1.5.2-amd64
Name: ghcr.io/thebinaryninja/tvapp2:1.5.4-amd64
MediaType: application/vnd.docker.distribution.manifest.v2+json
Digest: sha256:0abe1b1c119959b3b1ccc23c56a7ee2c4c908c6aaef290d4ab2993859d807a3b
$ docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.2-arm64
$ docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.4-arm64
Name: ghcr.io/thebinaryninja/tvapp2:1.5.2-arm64
Name: ghcr.io/thebinaryninja/tvapp2:1.5.4-arm64
MediaType: application/vnd.docker.distribution.manifest.v2+json
Digest: sha256:e68b9de8669eac64d4e4d2a8343c56705e05e9a907cf0b542343f9b536d9c473
```
@@ -989,14 +1066,14 @@ Digest: sha256:c719ccb034946e3f0625003f25026d001768794e38a1ba8aafc9146291d548
> ```shell
> $ docker images --all --no-trunc | grep thebinaryninja
>
> ghcr.io/thebinaryninja/tvapp2 1.5.2-arm64 sha256:48520ca15fed6483d2d5b79993126c311f833002345b0e12b8eceb5bf9def966 42 minutes ago 46MB
> ghcr.io/thebinaryninja/tvapp2 1.5.4-arm64 sha256:48520ca15fed6483d2d5b79993126c311f833002345b0e12b8eceb5bf9def966 42 minutes ago 46MB
>
> ghcr.io/thebinaryninja/tvapp2 1.5.2-amd64 sha256:54a9b7d390199532d5667fae67120d77e2f459bd6108b27ce94e0cfec8f3c41f 43 minutes ago 45MB
> ghcr.io/thebinaryninja/tvapp2 1.5.4-amd64 sha256:54a9b7d390199532d5667fae67120d77e2f459bd6108b27ce94e0cfec8f3c41f 43 minutes ago 45MB
> ```
>
> To get the correct sha256 digest, use:
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.2-amd64`
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.2-arm64`
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.4-amd64`
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.4-arm64`
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-amd64`
> - `docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-arm64`
>
@@ -1016,7 +1093,7 @@ For the **stable** releases, use:
# #
docker buildx imagetools create \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.2 \
--tag ghcr.io/thebinaryninja/tvapp2:1.5.4 \
--tag ghcr.io/thebinaryninja/tvapp2:1.5 \
--tag ghcr.io/thebinaryninja/tvapp2:1 \
--tag ghcr.io/thebinaryninja/tvapp2:latest \
@@ -1027,7 +1104,7 @@ docker buildx imagetools create \
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:latest 0.2s
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:1 0.2s
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:1.5 0.2s
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:1.5.2 0.2s
=> [internal] pushing ghcr.io/thebinaryninja/tvapp2:1.5.4 0.2s
```
<br />
@@ -1073,8 +1150,8 @@ In this example, we take the existing two files we created earlier, and merge th
```shell
# Example 1 (using tag)
docker manifest create ghcr.io/thebinaryninja/tvapp2:latest \
--amend ghcr.io/thebinaryninja/tvapp2:1.5.2-amd64 \
--amend ghcr.io/thebinaryninja/tvapp2:1.5.2-arm64
--amend ghcr.io/thebinaryninja/tvapp2:1.5.4-amd64 \
--amend ghcr.io/thebinaryninja/tvapp2:1.5.4-arm64
# Example 2 (using sha256 hash)
docker manifest create ghcr.io/thebinaryninja/tvapp2:latest \
@@ -1115,7 +1192,7 @@ To build the project, `🗔 cd` into the project folder and run the build comman
```shell
cd /home/docker/tvapp2/
npm run docker:build:amd64 --VERSION=1.5.2 --BUILDDATE=20260812
npm run docker:build:amd64 --VERSION=1.5.4 --BUILDDATE=20260812
```
<br />
@@ -1242,6 +1319,7 @@ This docker container contains the following env variables:
| `WEB_FOLDER` | `www` | Internal container folder to keep TVApp2 web files in. <br /><br /> <sup>⚠️ This should not be used unless you know what you're doing</sup> |
| `WEB_ENCODING` | `deflate, br` | Defines the HTTP `Accept-Encoding` request and response header. This value specifies what content encoding the sender can understand<br /><br />Gzip compression can be enabled by specifying `'gzip, deflate, br'`, however, [it may break Jellyfin users](#build-error-err-27-jellyfinlivetvguideguidemanager-error-getting-programs-for-channel-xxxxxxxxxxxxxxx-source-2-systemxmlxmlexception--hexadecimal-value-0x1f-is-an-invalid-character-line-1-position-1). |
| `WEB_PROXY_HEADER` | `x-forwarded-for` | Defines the header to look for when finding a client's IP address. Used to get a client's IP when behind a reverse proxy or Cloudflare |
| `HDHR_PORT` | `6077` | HDHomeRun server default listening port |
| `URL_REPO` | `https://git.binaryninja.net/BinaryNinja/` | Determines where the data files will be downloaded from. Do not change this or you will be unable to get M3U and EPG data. |
| `FILE_URL` | `urls.txt` | Filename for `urls.txt` cache file |
| `FILE_M3U` | `playlist.m3u8` | Filename for M3U playlist file |
@@ -1781,7 +1859,7 @@ Sign into the Authentik admin panel, go to the left-side navigation, select **Ap
<br />
<p align="center"><img style="width: 40%;text-align: center;" src="docs/img/authentik/01.png"><br><small><sup><b>Authentik:</b> Select <code>Applications</code> <code>Providers</code></sup></small></p>
<p align="center"><img style="width: 80%;text-align: center;" src="docs/img/authentik/01.png"><br><small><sup><b>Authentik:</b> Select <code>Applications</code> <code>Providers</code></sup></small></p>
<br />
@@ -1817,7 +1895,7 @@ Once finished, click **Create**. Then on the left-side menu, select **Applicatio
<br />
<p align="center"><img style="width: 40%;text-align: center;" src="docs/img/authentik/05.png"><br><small><sup><b>Authentik:</b> Select <code>Applications</code> <code>Applications</code></sup></small></p>
<p align="center"><img style="width: 80%;text-align: center;" src="docs/img/authentik/05.png"><br><small><sup><b>Authentik:</b> Select <code>Applications</code> <code>Applications</code></sup></small></p>
<br />
@@ -1843,7 +1921,7 @@ Save, and then on the left-side menu, select **Applications** -> **Outposts**:
<br />
<p align="center"><img style="width: 40%;text-align: center;" src="docs/img/authentik/07.png"><br><small><sup><b>Authentik:</b> Select <code>Applications</code> <code>Outposts</code></sup></small></p>
<p align="center"><img style="width: 80%;text-align: center;" src="docs/img/authentik/07.png"><br><small><sup><b>Authentik:</b> Select <code>Applications</code> <code>Outposts</code></sup></small></p>
<br />
@@ -1958,7 +2036,28 @@ If you have issues building your TVApp2 docker image, please refer to the follow
<br />
<br />
#### Run Error: `Error serving playlist: ENOENT: no such file or directory, open /usr/src/app/xmltv.1.xml`
### Run Error: `Warning: Step size 60 higher than possible maximum of 59`
This error means that you have placed an incorrect value for a cron job. This error can show if you've set:
```shell
environment:
TASK_CRON_SYNC: "*/60 * * * *"
```
<br />
To correctly set the value, change your cron to:
```shell
environment:
TASK_CRON_SYNC: "0 */1 * * *"
```
<br />
<br />
### Run Error: `Error serving playlist: ENOENT: no such file or directory, open /usr/src/app/xmltv.1.xml`
This error occurs at run-time when attempting to spin up your TVApp2 docker container. If you receive this error, restart your TVApp2 docker container. Ensure that your docker container also has access to your docker network so that it can connect to our repository and fetch the data files it needs to generate your playlist.
@@ -1969,7 +2068,7 @@ If the error continues after doing the above; delete the existing image, and re-
<br />
<br />
#### Build Error: `s6-rc-compile: fatal: invalid /etc/s6-overlay/s6-rc.d/certsync/type: must be oneshot, longrun, or bundle`
### Build Error: `s6-rc-compile: fatal: invalid /etc/s6-overlay/s6-rc.d/certsync/type: must be oneshot, longrun, or bundle`
This error means that you are attempting to combine files which are utilizing CRLF over LF; which is **CR** = Carriage Return and **LF** = Line Feed
@@ -2009,7 +2108,7 @@ find ./ -type f | grep -Ev '.git|*.jpg|*.jpeg|*.png' | sudo xargs dos2unix --
<br />
<br />
#### Build Error: `unable to exec /etc/s6-overlay/s6-rc.d/init-envfile/run: Permission denied`
### Build Error: `unable to exec /etc/s6-overlay/s6-rc.d/init-envfile/run: Permission denied`
There are multiple errors you can receive when attempting to run your TVApp2 docker image. You may receive any of the following errors:
@@ -2024,7 +2123,7 @@ There are multiple errors you can receive when attempting to run your TVApp2 doc
If you receive any of the above errors; this means that you have not set your `run` files to have execute permissions `+x`. Run the following command in the root directory of your TVApp2 project folder:
```shell
find ./ -name 'run' -exec sudo chmod +x {} \;
find ./ -name 'run' -print -exec sudo chmod +x {} \;
```
<br />
@@ -2034,7 +2133,7 @@ After you have set these permissions, re-build your docker image using `docker b
<br />
<br />
#### Build Error: `[ERR] [27] Jellyfin.LiveTv.Guide.GuideManager: Error getting programs for channel XXXXXXXXXXXXXXX (Source 2) System.Xml.XmlException: '', hexadecimal value 0x1F, is an invalid character. Line 1, position 1.`
### Build Error: `[ERR] [27] Jellyfin.LiveTv.Guide.GuideManager: Error getting programs for channel XXXXXXXXXXXXXXX (Source 2) System.Xml.XmlException: '', hexadecimal value 0x1F, is an invalid character. Line 1, position 1.`
This error may be seen if you are attempting to import our EPG guide data directly into Jellyfin. The cause of this is due to you having **GZIP Compression** enabled in your header request and response. See the example below; which is in your TVApp2 `📄 docker-compose.yml` file:

View File

@@ -45,6 +45,6 @@ services:
soft: -1
hard: -1
healthcheck:
test: [ "CMD", "curl", "--fail", "http://127.0.0.1:4124" ]
test: [ "CMD", "curl", "--fail", "http://127.0.0.1:4124/api/health?silent=true" ]
interval: 30s
retries: 5

View File

@@ -25,7 +25,7 @@
services:
# #
# Service TVApp2 Traefik Labels
# Service TVApp2
# #
tvapp2:
@@ -40,7 +40,6 @@ services:
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock
- ./config:/config
- ./app:/usr/bin/app
ulimits:
@@ -56,7 +55,7 @@ services:
- traefik.enable=true
# #
# Scope > http
# Routers Web Interface http
# #
- traefik.http.routers.tvapp2-http.rule=Host(`tvapp2.localhost`) || Host(`tvapp2.domain.lan`) || Host(`www.tvapp2.domain.lan`) || Host(`${SERVICE_IP}`)
@@ -66,7 +65,7 @@ services:
- traefik.http.routers.tvapp2-http.middlewares=https-redirect@file
# #
# Scope > https
# Routers Web Interface https
#
# remove the authentik@file line if you do not wish to use Authentik or middleware
# - traefik.http.routers.tvapp2-https.middlewares=authentik@file
@@ -83,8 +82,26 @@ services:
- traefik.http.routers.tvapp2-https.middlewares=authentik@file
# #
# Load Balancer
# Routers HDHomeRun
# #
- traefik.http.services.tvapp2.loadbalancer.server.port=http
- traefik.http.services.tvapp2.loadbalancer.server.scheme=4124
- traefik.http.routers.hdhr-https.rule=Host(`hdhr.domain.lan`)
- traefik.http.routers.hdhr-https.service=hdhr
- traefik.http.routers.hdhr-https.entrypoints=https
- traefik.http.routers.hdhr-https.priority=1
- traefik.http.routers.hdhr-https.tls=true
- traefik.http.routers.hdhr-https.tls.certresolver=cloudflare
# #
# Services Main Web Interface
# #
- traefik.http.services.tvapp2.loadbalancer.server.port=4124
- traefik.http.services.tvapp2.loadbalancer.server.scheme=http
# #
# Services HDHomeRun Server (optional)
# #
- traefik.http.services.hdhr.loadbalancer.server.port=6077
- traefik.http.services.hdhr.loadbalancer.server.scheme=http

View File

@@ -295,24 +295,24 @@ http:
- "*.domain.lan"
# #
# @container TVApp2
# @desc utomatic M3U playlist and XML guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client.
# @container TVApp2 Main
# @desc automatic M3U playlist and XML guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client.
# @url https://github.com/TheBinaryNinja/tvapp2
#
# remove / comment out the authentik line if you do not plan to use authentik:
# - authentik@file
# #
tvapp2-http:
service: "tvapp2"
tvapp2-server-http:
service: "tvapp2-server"
rule: "Host(`tvapp2.localhost`) || Host(`tvapp2.domain.lan`)"
entryPoints:
- http
middlewares:
- https-redirect@file
tvapp2-https:
service: "tvapp2"
tvapp2-server-https:
service: "tvapp2-server"
rule: "Host(`tvapp2.localhost`) || Host(`tvapp2.domain.lan`)"
entryPoints:
- https
@@ -325,6 +325,37 @@ http:
- main: "domain.lan"
sans:
- "*.domain.lan"
# #
# @container TVApp2 HDHomeRun
# @desc automatic M3U playlist and XML guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client.
# @url https://github.com/TheBinaryNinja/tvapp2
#
# remove / comment out the authentik line if you do not plan to use authentik:
# - authentik@file
# #
tvapp2-hdhr-http:
service: "tvapp2-hdhr"
rule: "Host(`hdhr.localhost`) || Host(`hdhr.domain.lan`)"
entryPoints:
- http
middlewares:
- https-redirect@file
tvapp2-hdhr-https:
service: "tvapp2-hdhr"
rule: "Host(`hdhr.localhost`) || Host(`hdhr.domain.lan`)"
entryPoints:
- https
middlewares:
- redirect-www@file
- authentik@file
tls:
certResolver: cloudflare
domains:
- main: "domain.lan"
sans:
- "*.domain.lan"
# #
# http Services
@@ -351,7 +382,12 @@ http:
servers:
- url: "http://plex:32400"
tvapp2:
tvapp2-server:
loadBalancer:
servers:
- url: "http://tvapp2:4124"
tvapp2-hdhr:
loadBalancer:
servers:
- url: "http://tvapp2:4124"

View File

@@ -77,6 +77,18 @@ declare -A icon=(
["sh"]=$'\xF0\x9F\x97\x94' # 🗔
)
# #
# distro info
# #
sys_os_name="Unknown"
sys_os_ver="1.0.0"
if [ -e /etc/alpine-release ]; then
sys_os_name="Alpine"
sys_os_ver="$(cat /etc/alpine-release)"
fi
# #
# get container ips
# #
@@ -141,6 +153,7 @@ printf '%-2s\n' " ${c[greyd]}and EPG files will be available for the public to d
if [[ -z ${TVAPP_NON_ROOT_USER} ]]; then
echo -e
printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}Distro${c[end]}" "${c[end]}${sys_os_name} ${sys_os_ver}${c[end]}"
printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}User:Group${c[end]}" "${c[end]}$(id -u dockerx):$(id -g dockerx)${c[end]}"
else
printf '%-6s %-35s %-65s\n' "" " ${c[greenl]}User:Group${c[end]}" "${c[end]}$(stat /run -c %u):$(stat /run -c %g)${c[end]}"

View File

@@ -78,11 +78,16 @@ declare -A icon=(
)
# #
# define > system
# distro info
# #
sys_os_ver="1.0.0"
sys_os_name="Unknown"
sys_os_ver="1.0.0"
if [ -e /etc/alpine-release ]; then
sys_os_name="Alpine"
sys_os_ver="$(cat /etc/alpine-release)"
fi
# #
# s6 > store env variables

169
tvapp2/classes/CLib.js Normal file
View File

@@ -0,0 +1,169 @@
/*
Compress / Uncompress String with base64
these functions use a unique character table. moving the letters around will cause strings to not
be in the correct order once uncompressed.
@usage new CLib().compress( 'https://daddylive.mp/' )
new CLib().uncompress( 'burS7u6FvUHhZfrhkfJoYz8CswTD=' )
new CLib().translate( '=', plugin.defTrans, plugin.tvaTrans )
a custom character set can be specified with two additional parameters. however, anything prior
that was encoded will not be decoded by the new character set.
const strCompress = new CLib().compress( 'test.com' );
const strUncompress = new CLib().uncompress( strCompress );
new CLib().compress( 'test.com', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'rXzxP9ZdvehYlstwiTuV1c07j45Abo2Ama6k3gqpyf8n+/NMSEIUHBQRJDLFCGKO' )
new CLib().uncompress( 'oZcUozDkAQH=', 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 'rXzxP9ZdvehYlstwiTuV1c07j45Abo2Ama6k3gqpyf8n+/NMSEIUHBQRJDLFCGKO' )
*/
import chalk from 'chalk';
import Log from './Log.js';
/*
Class > CLib
*/
class CLib
{
constructor()
{
this.defTrans = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
this.tvaTrans = 'TVAPp29uqXiv6g5adr1j8nfwZ0bs7Ykm3xl4hczAtoey+/CDKJULSEMBQRFGIHNO';
}
compress( data, defTrans, tvaTrans )
{
if ( typeof data === 'string' )
data = Buffer.from( data, 'utf8' );
const transDef = defTrans || this.defTrans;
const transTva = tvaTrans || this.tvaTrans;
try
{
const dataCompress = this.translate( data.toString( 'base64' ), transDef, transTva );
Log.ok( `clib`, chalk.yellow( `[compress]` ), chalk.white( `⚙️` ),
chalk.blueBright( `<msg>` ), chalk.gray( `Compress string` ),
chalk.blueBright( `<strRaw>` ), chalk.gray( `${ data }` ),
chalk.blueBright( `<strCompress>` ), chalk.gray( `${ dataCompress }` ) );
return dataCompress;
}
catch ( err )
{
Log.error( `clib`, chalk.redBright( `[compress]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Could not compress string; bad string ${ data }` ),
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ),
chalk.redBright( `<strCompress>` ), chalk.gray( `${ data }` ) );
return null;
}
}
uncompress( data, defTrans, tvaTrans )
{
if ( Buffer.isBuffer( data ) )
data = data.toString();
const transDef = defTrans || this.defTrans;
const transTva = tvaTrans || this.tvaTrans;
try
{
const dataTranslated = this.translate( data, transTva, transDef );
const dataUncompress = Buffer.from( dataTranslated, 'base64' ).toString( 'utf8' );
Log.ok( `clib`, chalk.yellow( `[decompss]` ), chalk.white( `⚙️` ),
chalk.blueBright( `<msg>` ), chalk.gray( `Uncompress string` ),
chalk.blueBright( `<strCompress>` ), chalk.gray( `${ data }` ),
chalk.blueBright( `<strRaw>` ), chalk.gray( `${ dataUncompress }` ) );
return dataUncompress;
}
catch ( err )
{
Log.error( `clib`, chalk.redBright( `[decompss]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Could not uncompress string; bad string ${ data }` ),
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ),
chalk.redBright( `<strCompress>` ), chalk.gray( `${ data }` ) );
return null;
}
}
/*
Translate
compresses or decompresses encoded strings for the functions:
- compress
- uncompress
*/
translate( str, fromChars, toChars )
{
let res = '';
for ( let i = 0;i < str.length;i++ )
{
const char = str[i];
const index = fromChars.indexOf( char );
if ( index !== -1 )
res += toChars[index];
else
res += char;
}
return res;
}
/*
Encode: String > Hex > Base64
encodes a human-readable string into a hex value, and then to base64
@usage const clib = new CLib()
const encoded = clib.encodeToHexBase64('hello'); // Njg2NTZjNmM2Zg==
const decoded = clib.decodeFromHexBase64(`${ encoded }`); // hello
*/
encodeToHexBase64( str )
{
const hex = [...str].map( ( char ) =>
{
const code = char.charCodeAt( 0 ).toString( 16 );
return code.padStart( 2, '0' );
}).join( '' );
const base64 = btoa( hex );
return base64;
}
/*
Decode: Base64 > Hex > String
decodes a base64 value to hex, and then back into a human readable string
@usage const clib = new CLib()
const encoded = clib.encodeToHexBase64('hello'); // Njg2NTZjNmM2Zg==
const decoded = clib.decodeFromHexBase64(`${ encoded }`); // hello
*/
decodeFromHexBase64( base64Str )
{
const hex = atob( base64Str );
const chars = hex.match( /.{1,2}/g ); // every 2 hex chars = 1 byte
return chars.map( ( byte ) => String.fromCharCode( parseInt( byte, 16 ) ) ).join( '' );
}
}
/*
export class
@usage import CLib from './classes/CLib.js';
*/
// eslint-disable-next-line no-restricted-syntax
export default CLib;

121
tvapp2/classes/Log.js Normal file
View File

@@ -0,0 +1,121 @@
/*
Define > Logs
When assigning text colors, terminals and the windows command prompt can display any color; however apps
such as Portainer console cannot. If you use 16 million colors and are viewing console in Portainer, colors will
not be the same as the rgb value. It's best to just stick to Chalk's default colors.
Various levels of logs with the following usage:
Log.verbose(`This is verbose`)
Log.debug(`This is debug`)
Log.info(`This is info`)
Log.ok(`This is ok`)
Log.notice(`This is notice`)
Log.warn(`This is warn`)
Log.error(
`Error fetching sports data with error:`,
chalk.white(`→`),
chalk.grey(`This is the error message`)
);
Level Type
-----------------------------------
6 Trace
5 Debug
4 Info
3 Notice
2 Warn
1 Error
*/
import fs from 'fs';
import chalk from 'chalk';
/*
chalk.level
@ref https://npmjs.com/package/chalk
- 0 All colors disabled
- 1 Basic color support (16 colors)
- 2 256 color support
- 3 Truecolor support (16 million colors)
When assigning text colors, terminals and the windows command prompt can display any color; however apps
such as Portainer console cannot. If you use 16 million colors and are viewing console in Portainer, colors will
not be the same as the rgb value. It's best to just stick to Chalk's default colors.
*/
chalk.level = 3;
/*
Define
*/
const LOG_LEVEL = process.env.LOG_LEVEL || 4;
const { name } = JSON.parse( fs.readFileSync( './package.json' ) );
/*
Class > Log
*/
class Log
{
static now()
{
const now = new Date();
return chalk.gray( `[${ now.toLocaleTimeString() }]` );
}
static verbose( ...msg )
{
if ( LOG_LEVEL >= 6 )
console.debug( chalk.white.bgBlack.blackBright.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.gray( msg.join( ' ' ) ) );
}
static debug( ...msg )
{
if ( LOG_LEVEL >= 7 )
console.trace( chalk.white.bgMagenta.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.magentaBright( msg.join( ' ' ) ) );
else if ( LOG_LEVEL >= 5 )
console.debug( chalk.white.bgGray.bold( ` ${ name } ` ), chalk.white( `⚙️` ), this.now(), chalk.gray( msg.join( ' ' ) ) );
}
static info( ...msg )
{
if ( LOG_LEVEL >= 4 )
console.info( chalk.white.bgBlueBright.bold( ` ${ name } ` ), chalk.white( `` ), this.now(), chalk.blueBright( msg.join( ' ' ) ) );
}
static ok( ...msg )
{
if ( LOG_LEVEL >= 4 )
console.log( chalk.white.bgGreen.bold( ` ${ name } ` ), chalk.white( `` ), this.now(), chalk.greenBright( msg.join( ' ' ) ) );
}
static notice( ...msg )
{
if ( LOG_LEVEL >= 3 )
console.log( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `📌` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) );
}
static warn( ...msg )
{
if ( LOG_LEVEL >= 2 )
console.warn( chalk.white.bgYellow.bold( ` ${ name } ` ), chalk.white( `⚠️` ), this.now(), chalk.yellowBright( msg.join( ' ' ) ) );
}
static error( ...msg )
{
if ( LOG_LEVEL >= 1 )
console.error( chalk.white.bgRedBright.bold( ` ${ name } ` ), chalk.white( `` ), this.now(), chalk.redBright( msg.join( ' ' ) ) );
}
}
/*
export class
@usage import Log from './classes/Log.js';
*/
// eslint-disable-next-line no-restricted-syntax
export default Log;

View File

@@ -0,0 +1,47 @@
/*
Semaphore > Declare
allows multiple threads to work with the same shared resources
*/
class Semaphore
{
constructor( max )
{
this.max = max;
this.queue = [];
this.active = 0;
}
async acquire()
{
if ( this.active < this.max )
{
this.active++;
return;
}
return new Promise( ( resolve ) => this.queue.push( resolve ) );
}
release()
{
this.active--;
if ( this.queue.length > 0 )
{
const resolve = this.queue.shift();
this.active++;
resolve();
}
}
}
/*
export class
@usage import Log from './classes/Log.js';
*/
// eslint-disable-next-line no-restricted-syntax
export default Semaphore;

520
tvapp2/classes/Storage.js Normal file
View File

@@ -0,0 +1,520 @@
/*
Class Storage
The storage classes allows you to save specific settings into a json file. These settings are better off being stored in
a local file, instead of using up the resources being saved in a database.
Class supports multiple storage files, but by default, it will save settings in `www/config.json`.
Settings include Tuner / HDHomeRun device information, etc.
@usage
const storage = new Storage( envWebFolder, FILE_CFG );
*/
import chalk from 'chalk';
import path from 'path';
import nconf from 'nconf';
import fs from 'fs';
import Log from './Log.js';
import Utils from './Utils.js';
import { fileURLToPath } from 'url';
/*
CJS ESM
*/
const __filename = fileURLToPath( import.meta.url ); // get resolved path to file
const __dirname = path.dirname( __filename ); // get name of directory
/*
Class Storage
constructor ( str:folder, str:file )
Initialize ( bool:bForceNew )
Setup ( bool:bForceNew )
Get ( str:key )
Set ( str:key, any:value )
Save ( )
GetConfig ( )
isJsonString ( json:str )
isJsonEmpty ( obj:json )
*/
class Storage
{
/*
Constructor Storage
Initializes a Storage instance for managing the config.json file.
Determines the full path to the config file based on folder and file arguments,
or uses the default static fileConfig if none are provided.
Handles Node.js packaged apps (process.pkg) by adjusting paths accordingly.
@args
folder (str) Optional folder where config.json will be stored. Defaults to 'www'.
file (str) Optional config file name. Defaults to static Storage.fileConfig.
@usage
const storage = new Storage(envWebFolder, FILE_CFG);
*/
static fileConfig = path.resolve( process.cwd( ), 'www', 'config.json' );
constructor( folder, file )
{
this.folderWeb = folder || 'www';
this.fileConfig = file ? path.resolve( folder, file ) : Storage.fileConfig;
if ( process.pkg )
this.fileConfig = path.join( path.dirname( process.execPath ), this.folderWeb, this.fileConfig );
else
this.fileConfig = path.resolve( process.cwd( ), this.folderWeb, this.fileConfig );
}
/*
Initialize Activate Config Setup with Logging
Activates the Storage.Setup( ) function while providing detailed logging.
Ensures the user's config.json file exists, is valid, and is initialized
with default values if missing or corrupt.
Steps:
- Logs the start of initialization.
- Calls Setup( ) with optional force flag to recreate config.
- Catches and logs any errors during setup.
@args
bForceNew (bool) Optional. If true, forces the config file to be removed
and regenerated from defaults.
@returns
(Promise) Resolves when initialization completes, or logs an error if setup fails.
@usage
const storage = new Storage(envWebFolder, FILE_CFG);
await storage.Initialize(false);
*/
async Initialize( bForceNew )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
const bForce = bForceNew || false;
try
{
Log.info( `conf`, chalk.yellow( `[initiate]` ), chalk.white( `` ),
chalk.blueBright( `<msg>` ), chalk.gray( `Initializing config file` ),
chalk.blueBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
await new Storage( ).Setup( bForce );
}
catch ( err )
{
console.log( 'Error writing Metadata.json:' + err.message );
}
}
/*
Initialize Setup User Config File
Sets up a user's config.json file, ensuring it exists and is valid JSON.
If the file is missing, empty, or invalid, it will be created or replaced.
Typically, you should call this via Storage( ).Initialize( ) rather than Setup( ) directly.
Steps:
- Creates parent directory if it doesn't exist.
- Removes existing config if bForceNew is true.
- Validates existing JSON; backs up invalid files.
- Creates default config if missing.
- Wires up nconf with argv, env, file, and default values.
@args
bForceNew (bool) Optional flag to force recreate the config file, wiping all existing data.
@returns
(Promise) Resolves true when initialization completes successfully.
@usage
const storage = new Storage(envWebFolder, FILE_CFG);
await storage.Initialize(false);
*/
async Setup( bForceNew )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
return new Promise( ( resolve, reject ) =>
{
try
{
Log.info( `conf`, chalk.yellow( `[generate]` ), chalk.white( `` ),
chalk.blueBright( `<msg>` ), chalk.gray( `Initializing storage setup` ),
chalk.blueBright( `<force>` ), chalk.gray( `${ bForceNew }` ),
chalk.blueBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
/*
ensure parent directory exists
*/
const dirPath = path.dirname( this.fileConfig );
if ( !fs.existsSync( dirPath ) )
{
fs.mkdirSync( dirPath, { recursive: true });
}
/*
if force flag is true, remove existing config file (force)
*/
if ( bForceNew === true && fs.existsSync( this.fileConfig ) )
{
Log.ok( `conf`, chalk.yellow( `[generate]` ), chalk.white( `` ),
chalk.greenBright( `<msg>` ), chalk.gray( `Remove original config; force new` ),
chalk.greenBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
try
{
fs.unlinkSync( this.fileConfig );
}
catch ( e )
{
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Failed to unlink existing config` ),
chalk.redBright( `<error>` ), chalk.gray( `${ e.message }` ),
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
}
}
/*
if config exists, validate JSON; if invalid, move to backup and recreate
*/
if ( fs.existsSync( this.fileConfig ) )
{
let raw = null;
let parsed = null;
try
{
raw = fs.readFileSync( this.fileConfig, { encoding: 'utf8' });
if ( typeof raw !== 'string' || raw.trim( ).length === 0 )
{
throw new Error( 'Empty config file' );
}
parsed = JSON.parse( raw );
}
catch ( e )
{
const backupPath = `${ this.fileConfig }.corrupt.${ Date.now( ) }`;
try
{
fs.renameSync( this.fileConfig, backupPath );
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Config file invalid; moved to backup` ),
chalk.redBright( `<backup>` ), chalk.gray( `${ backupPath }` ),
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
}
catch ( renameErr )
{
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Unable to backup invalid config file` ),
chalk.redBright( `<error>` ), chalk.gray( `${ renameErr.message }` ),
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
if ( this.rejected )
{
reject( renameErr );
return;
}
}
}
}
/*
if config does not exist (or was just moved because it was corrupt), create it atomically
*/
if ( !fs.existsSync( this.fileConfig ) )
{
const defaults =
{
deviceId: 'FFFFFFFF'
};
const tempPath = `${ this.fileConfig }.tmp`;
try
{
fs.writeFileSync( tempPath, JSON.stringify( defaults, null, 4 ), { encoding: 'utf8' });
fs.renameSync( tempPath, this.fileConfig );
Log.ok( `conf`, chalk.yellow( `[generate]` ), chalk.white( `` ),
chalk.greenBright( `<msg>` ), chalk.gray( `Created new config file with defaults` ),
chalk.greenBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
}
catch ( writeErr )
{
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Failed to create config file` ),
chalk.redBright( `<error>` ), chalk.gray( `${ writeErr.message }` ),
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
if ( this.rejected )
{
reject( writeErr );
return;
}
}
}
/*
now that file exists and is valid JSON, wire up nconf
*/
nconf.argv( ).env({ parseValues: true }).file({ file: this.fileConfig }).defaults(
{
deviceId: 'FFFFFFFF'
});
}
catch ( err )
{
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Could not generate and write to new config file` ),
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ),
chalk.redBright( `<file>` ), chalk.gray( `${ this.fileConfig }` ) );
if ( this.rejected )
{
reject( err );
return;
}
}
resolve( true );
});
}
/*
Get Retrieve Configuration Value
Fetches a stored value from the application's persistent configuration
using the provided key via the nconf module.
This function is static, so it can be called without creating a Storage instance.
@args
key (str) The configuration key to retrieve.
@returns
(any) The value associated with the key, or undefined if the key does not exist.
@usage
const deviceId = Storage.Get('deviceId');
*/
static Get( key )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
return nconf.get( key );
}
/*
Set Store Configuration Value
Stores a value in the application's persistent configuration using
the provided key via the nconf module. Automatically saves the
updated configuration to disk by calling Storage.Save( ).
This function is static, so it can be called without creating a Storage instance.
@args
key (str) The configuration key to set.
value (any) The value to store under the specified key.
@returns
(void) No return value.
@usage
Storage.Set('deviceId', '105B35EF');
*/
static Set( key, value )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ),
chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
nconf.set( key, value );
Storage.Save( );
}
/*
Save Persist Configuration to Disk
Saves the current configuration stored in nconf to disk.
After saving, the method reads back the file to verify it is valid JSON
and logs detailed status messages about success or errors.
@purpose
- Calls nconf.save() to write the current configuration.
- Reads back the saved file.
- Parses the file as JSON to confirm validity.
- Logs success or detailed error messages for failures.
@args
none
@returns
(void) Logs success or error; does not return a value.
@usage
Storage.Save();
*/
static Save( )
{
const filePath = this.fileConfig;
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ),
chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
nconf.save( ( err ) =>
{
if ( err )
{
Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Could not save config` ),
chalk.redBright( `<error>` ), chalk.gray( `${ err }` ),
chalk.redBright( `<file>` ), chalk.gray( `${ filePath }` ) );
return;
}
fs.readFile( filePath, ( err, data ) =>
{
if ( err )
{
Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Unable to read config file` ),
chalk.redBright( `<error>` ), chalk.gray( `${ err }` ),
chalk.redBright( `<file>` ), chalk.gray( `${ filePath }` ) );
return;
}
try
{
const parsed = JSON.parse( data.toString( ) );
Log.ok( `conf`, chalk.yellow( `[snapshot]` ), chalk.white( `` ),
chalk.greenBright( `<msg>` ), chalk.gray( `Save to config file successful` ),
chalk.greenBright( `<file>` ), chalk.gray( `${ filePath }` ) );
Log.debug( `conf`, chalk.yellow( `[snapshot]` ), chalk.white( `⚙️` ),
chalk.blueBright( `<msg>` ), chalk.gray( `Read values from saved config file` ),
chalk.blueBright( `<file>` ), chalk.gray( `${ filePath }` ),
chalk.blueBright( `<values>` ), chalk.gray( `${ JSON.stringify( parsed ) }` ) );
}
catch ( parseErr )
{
Log.error( `conf`, chalk.redBright( `[snapshot]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Config file is not valid JSON` ),
chalk.redBright( `<error>` ), chalk.gray( `${ parseErr.message }` ),
chalk.redBright( `<file>` ), chalk.gray( `${ filePath }` ) );
}
});
});
}
/*
GetConfig Return Full Path to Config File
Returns the full path to the currently used config.json file for this Storage instance.
This is useful when you need to know the exact file location without reading its contents.
@args
none
@returns
(str) Absolute path to the config.json file.
@usage
const storage_config = Storage.GetConfig();
*/
static GetConfig( )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
return this.fileConfig;
}
/*
isJsonString Check if Input is Valid JSON
Determines whether a given string is valid JSON by attempting
to parse it. Returns true if parsing succeeds, false if it throws
an error.
@args
json (str) The string to test for valid JSON.
@returns
(bool) True if input is valid JSON, false otherwise.
@usage
const valid = Storage.isJsonString('{"key":"value"}'); // returns true
*/
static isJsonString( json )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
try
{
JSON.parse( json );
}
catch ( e )
{
return false;
}
return true;
}
/*
helper json object empty
*/
static isJsonEmpty( json )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
if ( Object.keys( json ).length === 0 )
return true;
if ( JSON.stringify( json ) === '\"{}\"' )
return true;
for ( const key in json )
{
if ( ! Object.prototype.hasOwnProperty.call( json, key ) )
return true;
}
return false;
}
}
/*
export class
@import
import Storage from './classes/Storage.js';
*/
// eslint-disable-next-line no-restricted-syntax
export default Storage;

455
tvapp2/classes/Tuner.js Normal file
View File

@@ -0,0 +1,455 @@
/*
Class Tuner
Handles HDHomeRun device management and deviceId lifecycle.
@purpose
- Generate / format HDHomeRun device IDs.
- Validate device IDs against HDHomeRun rules (length, hex chars, checksum).
- Persist device IDs using Storage class.
- Automatically generate new device ID if missing, invalid, or uninitialized (FFFFFFFF).
- Initialize tuner instances with validated device IDs.
@usage
await new Tuner( Storage.Get( 'deviceId' ) ).Initialize( );
const tuner = new Tuner( );
await tuner.Initialize( );
const validId = await tuner.VerifyDeviceId( );
@notes
- Device IDs are persisted via the Storage class (config.json).
- User's device id must be valid before HDHomeRun will initialize.
*/
import chalk from 'chalk';
import Storage from './Storage.js';
import Utils from './Utils.js';
import Log from './Log.js';
/*
Class Tuner
constructor ( str:deviceId )
Initialize ( )
Start ( )
_GenerateDeviceId ( int:len )
GenerateDeviceId ( )
GetDeviceId ( )
FormatDeviceId ( str:deviceid )
IsDeviceIdValid ( )
VerifyDeviceId ( )
*/
class Tuner
{
constructor( deviceId )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getConstructorName( ) }` ) );
this.Name = `HDHomeRun`;
this.FriendlyName = `TVApp2`;
this.ModelNumber = `HDHR5-4US`;
this.FirmwareName = `hdhomerun5_atsc`;
this.FirmwareVersion = `0.9.15.00-RC04`;
this.SlotsConnected = 0;
this.SlotsMax = 10;
this.DeviceId = deviceId || Storage.Get( 'deviceId' );
}
/*
Initialize Setup and Start Tuner
Initializes the tuner by calling the Start( ) method.
Catches and logs any errors encountered during startup.
@args
none
@returns
(void) Logs status; does not return a value.
@usage
await tuner.Initialize( );
*/
async Initialize( )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
try
{
await this.Start( );
}
catch ( err )
{
Log.error( `hdhr`, chalk.redBright( `[initiate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Failure initializing tuner` ),
chalk.redBright( `<error>` ), chalk.gray( `${ err.message }` ) );
}
}
/*
Start Initialize and Verify Device ID
Starts the tuner by verifying the current deviceId.
If the deviceId is missing or invalid, it will be regenerated and validated.
Logs the status of the deviceId once verification completes.
@args
none
@returns
(bool) true if deviceId is valid after verification, false otherwise.
@usage
await tuner.Start( );
*/
async Start( )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
const verifiedId = await new Tuner( ).VerifyDeviceId( this.DeviceId );
if ( await this.IsDeviceIdValid( verifiedId ) )
{
Log.ok( `conf`, chalk.yellow( `[validate]` ), chalk.white( `` ),
chalk.greenBright( `<msg>` ), chalk.gray( `User has valid deviceId` ),
chalk.greenBright( `<deviceId>` ), chalk.gray( `${ verifiedId }` ) );
}
}
/*
_GenerateDeviceId Generate Raw Random Hexadecimal String
Generates a raw random hexadecimal string using Node.js crypto module.
This is typically used as the random portion of a deviceId.
@args
len (int) Optional number of bytes to generate. Defaults to 4 bytes.
@returns
(str) Uppercase hexadecimal string, length = len * 2 characters.
@usage
const randomHex = Tuner._GenerateDeviceId( 4 ); // 8-character hex string
*/
static _GenerateDeviceId( len )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
return crypto.randomBytes( len || 4 ).toString( 'hex' ).toUpperCase( );
}
/*
GenerateDeviceId Generate New HDHomeRun Device ID
Generates a new, properly formatted HDHomeRun deviceId.
Steps:
- Generates 4 random hexadecimal characters.
- Prepends '105' and appends '0' to form base deviceId.
- Passes baseId to Tuner.FormatDeviceId( ) to ensure correct checksum and 8-character format.
@args
None
@returns
(str) A valid, 8-character HDHomeRun deviceId in uppercase hexadecimal.
@usage
const newDeviceId = Tuner.GenerateDeviceId( );
*/
static GenerateDeviceId( )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
const chars = '0123456789ABCDEF';
let randomHex = '';
// generate 4 random hexadecimal chars
for ( let i = 0;i < 4;i++ )
{
randomHex += chars[Math.floor( Math.random( ) * chars.length )];
}
const baseId = '105' + randomHex + '0';
return this.FormatDeviceId( baseId );
}
/*
GetDeviceId Retrieve Stored HDHomeRun Device ID
Fetches the current deviceId from persistent storage (via Storage.Get).
@args
None
@returns
(str) The current deviceId stored in configuration.
@usage
const deviceId = await tuner.GetDeviceId( );
*/
GetDeviceId( )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
return Storage.Get( 'deviceId' );
}
/*
FormatDeviceId Validate and Format HDHomeRun Device ID
Fetches the provided deviceId (or instance default) and ensures it is valid
according to HDHomeRun rules, then returns a properly formatted ID.
Steps:
- Input must be exactly 8 hexadecimal characters.
- All characters must be 0-9 or A-F/a-f.
- Computes checksum using HDHomeRun-specific lookup table.
- Generates a new deviceId integer with checksum applied.
- Converts back to 8-character uppercase hexadecimal string.
Logs detailed errors if the input deviceId is invalid.
@args
deviceid (str) Optional deviceId to format. Defaults to instance deviceId.
@returns
(str|int) Formatted 8-character hex deviceId, or 0 if input invalid.
@usage
const formattedId = Tuner.FormatDeviceId( someDeviceId );
*/
static FormatDeviceId( deviceid )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
const deviceId = deviceid || this.DeviceId;
/*
Validate input length
*/
if ( !deviceId || deviceId.length !== 8 )
{
Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `HDHomeRun deviceId must be 8 hexadecimals` ),
chalk.redBright( `<deviceId>` ), chalk.gray( `${ deviceId }` ) );
return 0;
}
/*
All chars should be valid hexadecimal
*/
const hexPattern = /^[0-9A-Fa-f]+$/;
if ( !hexPattern.test( deviceId ) )
{
Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `HDHomeRun deviceId must contain all hex (0-9, A-F, a-f)` ),
chalk.redBright( `<deviceId>` ), chalk.gray( `${ deviceId }` ) );
return 0;
}
/*
Hex string to integer
*/
const deviceIdInt = parseInt( deviceId, 16 );
/*
Checksum lookup table
*/
const checksumLookup =
[
0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0
];
/*
Calc checksum
*/
let checksum = 0;
checksum ^= checksumLookup[( deviceIdInt >> 28 ) & 0x0F];
checksum ^= ( deviceIdInt >> 24 ) & 0x0F;
checksum ^= checksumLookup[( deviceIdInt >> 20 ) & 0x0F];
checksum ^= ( deviceIdInt >> 16 ) & 0x0F;
checksum ^= checksumLookup[( deviceIdInt >> 12 ) & 0x0F];
checksum ^= ( deviceIdInt >> 8 ) & 0x0F;
checksum ^= checksumLookup[( deviceIdInt >> 4 ) & 0x0F];
/*
Calc new device ID
*/
const newDevId = ( deviceIdInt & 0xFFFFFFF0 ) + checksum;
/*
Convert back to hex string; ensure we get 8 characters with leading zeros; convert to uppercase
*/
return newDevId.toString( 16 ).toUpperCase( ).padStart( 8, '0' );
}
/*
IsDeviceIdValid Validate HDHomeRun Device ID
Checks if the current deviceId on this instance is valid according to HDHomeRun rules.
Validation steps:
- Must be exactly 8 characters long.
- All characters must be hexadecimal (0-9, A-F, a-f).
- Computes checksum using HDHomeRun-specific lookup table; must equal 0.
Logs detailed errors if the deviceId fails any validation step.
@returns
(bool) true if deviceId is valid, false otherwise.
@usage
const isValid = await tuner.IsDeviceIdValid( );
*/
async IsDeviceIdValid( )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
/*
Define Hexadecimal charset (0-9, A-F, a-f)
*/
const hexDigits = new Set( '0123456789ABCDEFabcdef' );
const deviceId = this.DeviceId;
/*
Check if device ID is exactly 8 characters
*/
if ( !deviceId || deviceId.length !== 8 )
{
Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `HDHomeRun deviceId must be 8 hexadecimals` ),
chalk.redBright( `<deviceId>` ), chalk.gray( `${ deviceId }` ) );
return false;
}
/*
Check if all characters are hexadecimal
*/
if ( !Array.from( deviceId ).every( ( c ) => hexDigits.has( c ) ) )
{
Log.error( `hdhr`, chalk.redBright( `[validate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `HDHomeRun deviceId must contain all hex (0-A)` ),
chalk.redBright( `<deviceId>` ), chalk.gray( `${ deviceId }` ) );
return false;
}
/*
Convert hex string to integer (equivalent to int.from_bytes with big endian)
*/
const deviceIdInt = parseInt( deviceId, 16 );
/*
Checksum lookup table
*/
const checksumLookup =
[
0xA, 0x5, 0xF, 0x6, 0x7, 0xC, 0x1, 0xB, 0x9, 0x2, 0x8, 0xD, 0x4, 0x3, 0xE, 0x0
];
/*
Calc checksum
*/
let checksum = 0;
checksum ^= checksumLookup[( deviceIdInt >>> 28 ) & 0x0F];
checksum ^= ( deviceIdInt >>> 24 ) & 0x0F;
checksum ^= checksumLookup[( deviceIdInt >>> 20 ) & 0x0F];
checksum ^= ( deviceIdInt >>> 16 ) & 0x0F;
checksum ^= checksumLookup[( deviceIdInt >>> 12 ) & 0x0F];
checksum ^= ( deviceIdInt >>> 8 ) & 0x0F;
checksum ^= checksumLookup[( deviceIdInt >>> 4 ) & 0x0F];
checksum ^= ( deviceIdInt >>> 0 ) & 0x0F;
return checksum === 0;
}
/*
VerifyDeviceId Validate / Generate Device ID
Checks if the current deviceId on this instance is valid.
If missing, uninitialized ('FFFFFFFF'), or fails validation:
a new deviceId is generated via the static Tuner.GenerateDeviceId( ) method.
New deviceId is saved to persistent storage via Storage.Set( ) and
updated on the instance.
Function also recursively verifies until a valid deviceId is established.
@returns
(str) A valid deviceId for this tuner instance.
@usage
const validId = await tuner.VerifyDeviceId( );
*/
async VerifyDeviceId( )
{
Log.verbose( `func`, chalk.yellow( `[executed]` ), chalk.white( `📣` ), chalk.blueBright( `<name>` ), chalk.gray( `${ Utils.getFuncName( ) }` ) );
const deviceId = this.DeviceId;
if ( !deviceId || deviceId === 'FFFFFFFF' || !await this.IsDeviceIdValid( ) )
{
const deviceIdNew = Tuner.GenerateDeviceId( ); // static generates a properly formatted ID
if ( deviceId === 'FFFFFFFF' )
{
Log.info( `conf`, chalk.yellow( `[generate]` ), chalk.white( `📣` ),
chalk.yellow( `<msg>` ), chalk.gray( `Generating HDHomeRun deviceId for the first time` ),
chalk.yellow( `<deviceId>` ), chalk.gray( `${ deviceIdNew }` ) );
}
else
{
Log.error( `conf`, chalk.redBright( `[generate]` ), chalk.white( `` ),
chalk.redBright( `<msg>` ), chalk.gray( `Invalid deviceId; generating new` ),
chalk.redBright( `<oldDeviceId>` ), chalk.gray( `${ deviceId }` ),
chalk.redBright( `<deviceIdNew>` ), chalk.gray( `${ deviceIdNew }` ) );
}
Storage.Set( 'deviceId', deviceIdNew ); // save to JSON via nconf
this.DeviceId = deviceIdNew; // update the instance so validation works
// verify recursively until valid
const verifiedId = await this.VerifyDeviceId( );
return verifiedId;
}
return deviceId;
}
}
/*
export class
@image
import Tuner from './classes/Tuner.js';
*/
// eslint-disable-next-line no-restricted-syntax
export default Tuner;

47
tvapp2/classes/Utils.js Normal file
View File

@@ -0,0 +1,47 @@
class Utils
{
/*
Returns the name of the function that this function was called from.
used for Log.verbose
*/
static getFuncName()
{
return ( new Error() ).stack.match( /at (\S+)/g )[1].slice( 3 );
}
/*
Returns the name of the constructor that this function was called from.
used for Log.verbose
*/
static getConstructorName()
{
return ( new Error() ).stack.match( /new\s+(\w+)/g )[0];
}
/*
helper > str2bool
*/
static str2bool( str )
{
if ( typeof str === 'string' )
{
const lower = str.toLowerCase();
if ([
'1', 'true', 'yes', 'y', 't'
].includes( lower ) )
str = true;
if ([
'0', 'false', 'no', 'n', 'f'
].includes( lower ) )
str = false;
return str;
}
else return Boolean( str );
}
}
// eslint-disable-next-line no-restricted-syntax
export default Utils;

View File

@@ -23,7 +23,7 @@ import { FlatCompat } from '@eslint/eslintrc';
import pluginImport from 'eslint-plugin-import';
import pluginNode from 'eslint-plugin-n'
import pluginChaiFriendly from 'eslint-plugin-chai-friendly';
import pluginStylisticJs from '@stylistic/eslint-plugin-js'
import pluginStylistic from '@stylistic/eslint-plugin'
/*
Globals
@@ -75,7 +75,7 @@ export default
plugins: {
'n': pluginNode,
'import': pluginImport,
'@stylistic/js': pluginStylisticJs,
'@stylistic': pluginStylistic,
'chai-friendly': pluginChaiFriendly
},
linterOptions: {
@@ -145,7 +145,7 @@ export default
'import/no-webpack-loader-syntax': 'off',
'import/no-relative-parent-imports': 'error',
'import/first': 'error',
'import/no-default-export': 'error',
'import/no-default-export': 'off',
'node/no-callback-literal': 0,
/*
@@ -191,68 +191,68 @@ export default
'n/prefer-promises/dns': 'off',
'n/prefer-promises/fs': 'off',
'n/process-exit-as-throw': 'error',
'@stylistic/js/object-property-newline': 'off',
'@stylistic/js/no-multi-spaces': [ 0, { ignoreEOLComments: true } ],
'@stylistic/js/arrow-spacing': [ 'error', { before: true, after: true } ],
'@stylistic/js/semi-spacing': ['error', {
'@stylistic/object-property-newline': 'off',
'@stylistic/no-multi-spaces': [ 0, { ignoreEOLComments: true } ],
'@stylistic/arrow-spacing': [ 'error', { before: true, after: true } ],
'@stylistic/semi-spacing': ['error', {
before: false,
after: false,
}],
"@stylistic/js/space-before-function-paren": ["error", {
"@stylistic/space-before-function-paren": ["error", {
anonymous: "always",
asyncArrow: "never",
named: "never"
}],
'@stylistic/js/padded-blocks': ['error', {
'@stylistic/padded-blocks': ['error', {
blocks: 'never',
switches: 'never',
classes: 'never',
}],
'@stylistic/js/arrow-parens': [ 'error', 'always' ],
'@stylistic/js/block-spacing': [ 'error', 'always' ],
'@stylistic/js/comma-dangle': [ 'error', 'never' ],
'@stylistic/js/comma-spacing': [ 'error', { before: false, after: true }],
'@stylistic/js/computed-property-spacing': ['error', 'never'],
'@stylistic/js/no-mixed-operators': ['off'],
'@stylistic/js/eol-last': ['error', 'always'],
'@stylistic/js/jsx-quotes': ['error', 'prefer-single'],
'@stylistic/js/linebreak-style': ['error', 'unix'],
'@stylistic/js/no-mixed-spaces-and-tabs': ['error'],
'@stylistic/js/no-tabs': ['error'],
'@stylistic/js/no-trailing-spaces': ['error', { skipBlankLines: true, ignoreComments: true }],
'@stylistic/js/no-whitespace-before-property': ['error'],
'@stylistic/js/object-curly-spacing': ['error', 'always'],
'@stylistic/js/quote-props': ['error', 'as-needed'],
'@stylistic/js/quotes': ['error', 'single', { allowTemplateLiterals: true }],
'@stylistic/js/semi': ['error', 'always'],
'@stylistic/js/space-infix-ops': ['error'],
'@stylistic/js/template-curly-spacing': ['error', 'always'],
'@stylistic/js/template-tag-spacing': ['error', 'always'],
'@stylistic/js/space-in-parens': [ 'error', 'always',
'@stylistic/arrow-parens': [ 'error', 'always' ],
'@stylistic/block-spacing': [ 'error', 'always' ],
'@stylistic/comma-dangle': [ 'error', 'never' ],
'@stylistic/comma-spacing': [ 'error', { before: false, after: true }],
'@stylistic/computed-property-spacing': ['error', 'never'],
'@stylistic/no-mixed-operators': ['off'],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/jsx-quotes': ['error', 'prefer-single'],
'@stylistic/linebreak-style': ['error', 'unix'],
'@stylistic/no-mixed-spaces-and-tabs': ['error'],
'@stylistic/no-tabs': ['error'],
'@stylistic/no-trailing-spaces': ['error', { skipBlankLines: true, ignoreComments: true }],
'@stylistic/no-whitespace-before-property': ['error'],
'@stylistic/object-curly-spacing': ['error', 'always'],
'@stylistic/quote-props': ['error', 'as-needed'],
'@stylistic/quotes': ['error', 'single', { allowTemplateLiterals: 'always' }],
'@stylistic/semi': ['error', 'always'],
'@stylistic/space-infix-ops': ['error'],
'@stylistic/template-curly-spacing': ['error', 'always'],
'@stylistic/template-tag-spacing': ['error', 'always'],
'@stylistic/space-in-parens': [ 'error', 'always',
{
exceptions: ["{}", "[]"]
}],
'@stylistic/js/spaced-comment': [ 'error', 'always',
'@stylistic/spaced-comment': [ 'error', 'always',
{
markers: ['/']
}],
'@stylistic/js/array-bracket-newline': [ 'warn',
'@stylistic/array-bracket-newline': [ 'warn',
{
multiline: true,
minItems: 5,
}],
'@stylistic/js/brace-style': [ 'error', 'allman',
'@stylistic/brace-style': [ 'error', 'allman',
{
allowSingleLine: true,
}],
'@stylistic/js/array-bracket-spacing': [ 'error', 'always',
'@stylistic/array-bracket-spacing': [ 'error', 'always',
{
arraysInArrays: false,
objectsInArrays: false,
singleValue: false,
}],
'@stylistic/js/wrap-iife': [2, 'inside', { functionPrototypeMethods: true }],
'@stylistic/js/keyword-spacing': [ 'error',
'@stylistic/wrap-iife': [2, 'inside', { functionPrototypeMethods: true }],
'@stylistic/keyword-spacing': [ 'error',
{
before: true,
after: true,

File diff suppressed because it is too large Load Diff

16
tvapp2/node_modules/.bin/playwright generated vendored
View File

@@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../playwright/cli.js" "$@"
else
exec node "$basedir/../playwright/cli.js" "$@"
fi

View File

@@ -1,16 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*)
if command -v cygpath > /dev/null 2>&1; then
basedir=`cygpath -w "$basedir"`
fi
;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../playwright-core/cli.js" "$@"
else
exec node "$basedir/../playwright-core/cli.js" "$@"
fi

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%\..\playwright-core\cli.js" %*

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/../playwright-core/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../playwright-core/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../playwright-core/cli.js" $args
} else {
& "node$exe" "$basedir/../playwright-core/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

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%\..\playwright\cli.js" %*

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/../playwright/cli.js" $args
} else {
& "$basedir/node$exe" "$basedir/../playwright/cli.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../playwright/cli.js" $args
} else {
& "node$exe" "$basedir/../playwright/cli.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

4637
tvapp2/node_modules/.package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +0,0 @@
Copyright jQuery Foundation and other contributors <https://jquery.org/>
Based on Underscore.js, copyright Jeremy Ashkenas,
DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
This software consists of voluntary contributions made by many
individuals. For exact contribution history, see the revision history
available at https://github.com/lodash/lodash
The following license applies to all parts of this software except as
documented below:
====
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.
====
Copyright and related rights for sample code are waived via CC0. Sample
code is defined as all source code displayed within the prose of the
documentation.
CC0: http://creativecommons.org/publicdomain/zero/1.0/
====
Files located in the node_modules and vendor directories are externally
maintained libraries used by this software which have their own
licenses; we recommend you read them, as their terms may differ from the
terms above.

View File

@@ -1,18 +0,0 @@
# lodash.clonedeep v4.5.0
The [lodash](https://lodash.com/) method `_.cloneDeep` exported as a [Node.js](https://nodejs.org/) module.
## Installation
Using npm:
```bash
$ {sudo -H} npm i -g npm
$ npm i --save lodash.clonedeep
```
In Node.js:
```js
var cloneDeep = require('lodash.clonedeep');
```
See the [documentation](https://lodash.com/docs#cloneDeep) or [package source](https://github.com/lodash/lodash/blob/4.5.0-npm-packages/lodash.clonedeep) for more details.

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
{
"name": "lodash.clonedeep",
"version": "4.5.0",
"description": "The lodash method `_.cloneDeep` exported as a module.",
"homepage": "https://lodash.com/",
"icon": "https://lodash.com/icon.svg",
"license": "MIT",
"keywords": "lodash-modularized, clonedeep",
"author": "John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"contributors": [
"John-David Dalton <john.david.dalton@gmail.com> (http://allyoucanleet.com/)",
"Blaine Bublitz <blaine.bublitz@gmail.com> (https://github.com/phated)",
"Mathias Bynens <mathias@qiwi.be> (https://mathiasbynens.be/)"
],
"repository": "lodash/lodash",
"scripts": { "test": "echo \"See https://travis-ci.org/lodash/lodash-cli for testing details.\"" }
}

View File

@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Portions Copyright (c) Microsoft Corporation.
Portions Copyright 2017 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,5 +0,0 @@
Playwright
Copyright (c) Microsoft Corporation
This software contains code derived from the Puppeteer project (https://github.com/puppeteer/puppeteer),
available under the Apache 2.0 license (https://github.com/puppeteer/puppeteer/blob/master/LICENSE).

View File

@@ -1,3 +0,0 @@
# playwright-core
This package contains the no-browser flavor of [Playwright](http://github.com/microsoft/playwright).

View File

@@ -1,5 +0,0 @@
$osInfo = Get-WmiObject -Class Win32_OperatingSystem
# check if running on Windows Server
if ($osInfo.ProductType -eq 3) {
Install-WindowsFeature Server-Media-Foundation
}

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
if [[ $(arch) == "aarch64" ]]; then
echo "ERROR: not supported on Linux Arm64"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old beta if any.
if dpkg --get-selections | grep -q "^google-chrome-beta[[:space:]]*install$" >/dev/null; then
apt-get remove -y google-chrome-beta
fi
# 2. Update apt lists (needed to install curl and chrome dependencies)
apt-get update
# 3. Install curl to download chrome
if ! command -v curl >/dev/null; then
apt-get install -y curl
fi
# 4. download chrome beta from dl.google.com and install it.
cd /tmp
curl -O https://dl.google.com/linux/direct/google-chrome-beta_current_amd64.deb
apt-get install -y ./google-chrome-beta_current_amd64.deb
rm -rf ./google-chrome-beta_current_amd64.deb
cd -
google-chrome-beta --version

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
rm -rf "/Applications/Google Chrome Beta.app"
cd /tmp
curl -o ./googlechromebeta.dmg -k https://dl.google.com/chrome/mac/universal/beta/googlechromebeta.dmg
hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechromebeta.dmg ./googlechromebeta.dmg
cp -pR "/Volumes/googlechromebeta.dmg/Google Chrome Beta.app" /Applications
hdiutil detach /Volumes/googlechromebeta.dmg
rm -rf /tmp/googlechromebeta.dmg
/Applications/Google\ Chrome\ Beta.app/Contents/MacOS/Google\ Chrome\ Beta --version

View File

@@ -1,24 +0,0 @@
$ErrorActionPreference = 'Stop'
$url = 'https://dl.google.com/tag/s/dl/chrome/install/beta/googlechromebetastandaloneenterprise64.msi'
Write-Host "Downloading Google Chrome Beta"
$wc = New-Object net.webclient
$msiInstaller = "$env:temp\google-chrome-beta.msi"
$wc.Downloadfile($url, $msiInstaller)
Write-Host "Installing Google Chrome Beta"
$arguments = "/i `"$msiInstaller`" /quiet"
Start-Process msiexec.exe -ArgumentList $arguments -Wait
Remove-Item $msiInstaller
$suffix = "\\Google\\Chrome Beta\\Application\\chrome.exe"
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
(Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
(Get-Item "${env:ProgramFiles}$suffix").VersionInfo
} else {
Write-Host "ERROR: Failed to install Google Chrome Beta."
Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
exit 1
}

View File

@@ -1,42 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
if [[ $(arch) == "aarch64" ]]; then
echo "ERROR: not supported on Linux Arm64"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old stable if any.
if dpkg --get-selections | grep -q "^google-chrome[[:space:]]*install$" >/dev/null; then
apt-get remove -y google-chrome
fi
# 2. Update apt lists (needed to install curl and chrome dependencies)
apt-get update
# 3. Install curl to download chrome
if ! command -v curl >/dev/null; then
apt-get install -y curl
fi
# 4. download chrome stable from dl.google.com and install it.
cd /tmp
curl -O https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt-get install -y ./google-chrome-stable_current_amd64.deb
rm -rf ./google-chrome-stable_current_amd64.deb
cd -
google-chrome --version

View File

@@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
rm -rf "/Applications/Google Chrome.app"
cd /tmp
curl -o ./googlechrome.dmg -k https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg
hdiutil attach -nobrowse -quiet -noautofsck -noautoopen -mountpoint /Volumes/googlechrome.dmg ./googlechrome.dmg
cp -pR "/Volumes/googlechrome.dmg/Google Chrome.app" /Applications
hdiutil detach /Volumes/googlechrome.dmg
rm -rf /tmp/googlechrome.dmg
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version

View File

@@ -1,24 +0,0 @@
$ErrorActionPreference = 'Stop'
$url = 'https://dl.google.com/tag/s/dl/chrome/install/googlechromestandaloneenterprise64.msi'
$wc = New-Object net.webclient
$msiInstaller = "$env:temp\google-chrome.msi"
Write-Host "Downloading Google Chrome"
$wc.Downloadfile($url, $msiInstaller)
Write-Host "Installing Google Chrome"
$arguments = "/i `"$msiInstaller`" /quiet"
Start-Process msiexec.exe -ArgumentList $arguments -Wait
Remove-Item $msiInstaller
$suffix = "\\Google\\Chrome\\Application\\chrome.exe"
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
(Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
(Get-Item "${env:ProgramFiles}$suffix").VersionInfo
} else {
Write-Host "ERROR: Failed to install Google Chrome."
Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
exit 1
}

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
if [[ $(arch) == "aarch64" ]]; then
echo "ERROR: not supported on Linux Arm64"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old beta if any.
if dpkg --get-selections | grep -q "^microsoft-edge-beta[[:space:]]*install$" >/dev/null; then
apt-get remove -y microsoft-edge-beta
fi
# 2. Install curl to download Microsoft gpg key
if ! command -v curl >/dev/null; then
apt-get update
apt-get install -y curl
fi
# GnuPG is not preinstalled in slim images
if ! command -v gpg >/dev/null; then
apt-get update
apt-get install -y gpg
fi
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list'
rm /tmp/microsoft.gpg
apt-get update && apt-get install -y microsoft-edge-beta
microsoft-edge-beta --version

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
cd /tmp
curl -o ./msedge_beta.pkg -k "$1"
# Note: there's no way to uninstall previously installed MSEdge.
# However, running PKG again seems to update installation.
sudo installer -pkg /tmp/msedge_beta.pkg -target /
rm -rf /tmp/msedge_beta.pkg
/Applications/Microsoft\ Edge\ Beta.app/Contents/MacOS/Microsoft\ Edge\ Beta --version

View File

@@ -1,23 +0,0 @@
$ErrorActionPreference = 'Stop'
$url = $args[0]
Write-Host "Downloading Microsoft Edge Beta"
$wc = New-Object net.webclient
$msiInstaller = "$env:temp\microsoft-edge-beta.msi"
$wc.Downloadfile($url, $msiInstaller)
Write-Host "Installing Microsoft Edge Beta"
$arguments = "/i `"$msiInstaller`" /quiet"
Start-Process msiexec.exe -ArgumentList $arguments -Wait
Remove-Item $msiInstaller
$suffix = "\\Microsoft\\Edge Beta\\Application\\msedge.exe"
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
(Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
(Get-Item "${env:ProgramFiles}$suffix").VersionInfo
} else {
Write-Host "ERROR: Failed to install Microsoft Edge Beta."
Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
exit 1
}

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
if [[ $(arch) == "aarch64" ]]; then
echo "ERROR: not supported on Linux Arm64"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old dev if any.
if dpkg --get-selections | grep -q "^microsoft-edge-dev[[:space:]]*install$" >/dev/null; then
apt-get remove -y microsoft-edge-dev
fi
# 2. Install curl to download Microsoft gpg key
if ! command -v curl >/dev/null; then
apt-get update
apt-get install -y curl
fi
# GnuPG is not preinstalled in slim images
if ! command -v gpg >/dev/null; then
apt-get update
apt-get install -y gpg
fi
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list'
rm /tmp/microsoft.gpg
apt-get update && apt-get install -y microsoft-edge-dev
microsoft-edge-dev --version

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
cd /tmp
curl -o ./msedge_dev.pkg -k "$1"
# Note: there's no way to uninstall previously installed MSEdge.
# However, running PKG again seems to update installation.
sudo installer -pkg /tmp/msedge_dev.pkg -target /
rm -rf /tmp/msedge_dev.pkg
/Applications/Microsoft\ Edge\ Dev.app/Contents/MacOS/Microsoft\ Edge\ Dev --version

View File

@@ -1,23 +0,0 @@
$ErrorActionPreference = 'Stop'
$url = $args[0]
Write-Host "Downloading Microsoft Edge Dev"
$wc = New-Object net.webclient
$msiInstaller = "$env:temp\microsoft-edge-dev.msi"
$wc.Downloadfile($url, $msiInstaller)
Write-Host "Installing Microsoft Edge Dev"
$arguments = "/i `"$msiInstaller`" /quiet"
Start-Process msiexec.exe -ArgumentList $arguments -Wait
Remove-Item $msiInstaller
$suffix = "\\Microsoft\\Edge Dev\\Application\\msedge.exe"
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
(Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
(Get-Item "${env:ProgramFiles}$suffix").VersionInfo
} else {
Write-Host "ERROR: Failed to install Microsoft Edge Dev."
Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
exit 1
}

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
if [[ $(arch) == "aarch64" ]]; then
echo "ERROR: not supported on Linux Arm64"
exit 1
fi
if [ -z "$PLAYWRIGHT_HOST_PLATFORM_OVERRIDE" ]; then
if [[ ! -f "/etc/os-release" ]]; then
echo "ERROR: cannot install on unknown linux distribution (/etc/os-release is missing)"
exit 1
fi
ID=$(bash -c 'source /etc/os-release && echo $ID')
if [[ "${ID}" != "ubuntu" && "${ID}" != "debian" ]]; then
echo "ERROR: cannot install on $ID distribution - only Ubuntu and Debian are supported"
exit 1
fi
fi
# 1. make sure to remove old stable if any.
if dpkg --get-selections | grep -q "^microsoft-edge-stable[[:space:]]*install$" >/dev/null; then
apt-get remove -y microsoft-edge-stable
fi
# 2. Install curl to download Microsoft gpg key
if ! command -v curl >/dev/null; then
apt-get update
apt-get install -y curl
fi
# GnuPG is not preinstalled in slim images
if ! command -v gpg >/dev/null; then
apt-get update
apt-get install -y gpg
fi
# 3. Add the GPG key, the apt repo, update the apt cache, and install the package
curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > /tmp/microsoft.gpg
install -o root -g root -m 644 /tmp/microsoft.gpg /etc/apt/trusted.gpg.d/
sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-stable.list'
rm /tmp/microsoft.gpg
apt-get update && apt-get install -y microsoft-edge-stable
microsoft-edge-stable --version

View File

@@ -1,11 +0,0 @@
#!/usr/bin/env bash
set -e
set -x
cd /tmp
curl -o ./msedge_stable.pkg -k "$1"
# Note: there's no way to uninstall previously installed MSEdge.
# However, running PKG again seems to update installation.
sudo installer -pkg /tmp/msedge_stable.pkg -target /
rm -rf /tmp/msedge_stable.pkg
/Applications/Microsoft\ Edge.app/Contents/MacOS/Microsoft\ Edge --version

View File

@@ -1,24 +0,0 @@
$ErrorActionPreference = 'Stop'
$url = $args[0]
Write-Host "Downloading Microsoft Edge"
$wc = New-Object net.webclient
$msiInstaller = "$env:temp\microsoft-edge-stable.msi"
$wc.Downloadfile($url, $msiInstaller)
Write-Host "Installing Microsoft Edge"
$arguments = "/i `"$msiInstaller`" /quiet"
Start-Process msiexec.exe -ArgumentList $arguments -Wait
Remove-Item $msiInstaller
$suffix = "\\Microsoft\\Edge\\Application\\msedge.exe"
if (Test-Path "${env:ProgramFiles(x86)}$suffix") {
(Get-Item "${env:ProgramFiles(x86)}$suffix").VersionInfo
} elseif (Test-Path "${env:ProgramFiles}$suffix") {
(Get-Item "${env:ProgramFiles}$suffix").VersionInfo
} else {
Write-Host "ERROR: Failed to install Microsoft Edge."
Write-Host "ERROR: This could be due to insufficient privileges, in which case re-running as Administrator may help."
exit 1
}

View File

@@ -1,80 +0,0 @@
{
"comment": "Do not edit this file, use utils/roll_browser.js",
"browsers": [
{
"name": "chromium",
"revision": "1169",
"installByDefault": true,
"browserVersion": "136.0.7103.25"
},
{
"name": "chromium-headless-shell",
"revision": "1169",
"installByDefault": true,
"browserVersion": "136.0.7103.25"
},
{
"name": "chromium-tip-of-tree",
"revision": "1320",
"installByDefault": false,
"browserVersion": "137.0.7105.0"
},
{
"name": "chromium-tip-of-tree-headless-shell",
"revision": "1320",
"installByDefault": false,
"browserVersion": "137.0.7105.0"
},
{
"name": "firefox",
"revision": "1482",
"installByDefault": true,
"browserVersion": "137.0"
},
{
"name": "firefox-beta",
"revision": "1477",
"installByDefault": false,
"browserVersion": "137.0b2"
},
{
"name": "webkit",
"revision": "2158",
"installByDefault": true,
"revisionOverrides": {
"debian11-x64": "2105",
"debian11-arm64": "2105",
"mac10.14": "1446",
"mac10.15": "1616",
"mac11": "1816",
"mac11-arm64": "1816",
"mac12": "2009",
"mac12-arm64": "2009",
"mac13": "2140",
"mac13-arm64": "2140",
"ubuntu20.04-x64": "2092",
"ubuntu20.04-arm64": "2092"
},
"browserVersion": "18.4"
},
{
"name": "ffmpeg",
"revision": "1011",
"installByDefault": true,
"revisionOverrides": {
"mac12": "1010",
"mac12-arm64": "1010"
}
},
{
"name": "winldd",
"revision": "1007",
"installByDefault": false
},
{
"name": "android",
"revision": "1001",
"installByDefault": false
}
]
}

View File

@@ -1,18 +0,0 @@
#!/usr/bin/env node
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const { program } = require('./lib/cli/programWithTestStub');
program.parse(process.argv);

View File

@@ -1,17 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './types/types';

View File

@@ -1,32 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const minimumMajorNodeVersion = 14;
const currentNodeVersion = process.versions.node;
const semver = currentNodeVersion.split('.');
const [major] = [+semver[0]];
if (major < minimumMajorNodeVersion) {
console.error(
'You are running Node.js ' +
currentNodeVersion +
'.\n' +
`Playwright requires Node.js ${minimumMajorNodeVersion} or higher. \n` +
'Please update your version of Node.js.'
);
process.exit(1);
}
module.exports = require('./lib/inprocess');

View File

@@ -1,28 +0,0 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import playwright from './index.js';
export const chromium = playwright.chromium;
export const firefox = playwright.firefox;
export const webkit = playwright.webkit;
export const selectors = playwright.selectors;
export const devices = playwright.devices;
export const errors = playwright.errors;
export const request = playwright.request;
export const _electron = playwright._electron;
export const _android = playwright._android;
export default playwright;

View File

@@ -1,63 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var androidServerImpl_exports = {};
__export(androidServerImpl_exports, {
AndroidServerLauncherImpl: () => AndroidServerLauncherImpl
});
module.exports = __toCommonJS(androidServerImpl_exports);
var import_playwrightServer = require("./remote/playwrightServer");
var import_playwright = require("./server/playwright");
var import_crypto = require("./server/utils/crypto");
var import_utilsBundle = require("./utilsBundle");
class AndroidServerLauncherImpl {
async launchServer(options = {}) {
const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
let devices = await playwright.android.devices({
host: options.adbHost,
port: options.adbPort,
omitDriverInstall: options.omitDriverInstall
});
if (devices.length === 0)
throw new Error("No devices found");
if (options.deviceSerialNumber) {
devices = devices.filter((d) => d.serial === options.deviceSerialNumber);
if (devices.length === 0)
throw new Error(`No device with serial number '${options.deviceSerialNumber}' was found`);
}
if (devices.length > 1)
throw new Error(`More than one device found. Please specify deviceSerialNumber`);
const device = devices[0];
const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`;
const server = new import_playwrightServer.PlaywrightServer({ mode: "launchServer", path, maxConnections: 1, preLaunchedAndroidDevice: device });
const wsEndpoint = await server.listen(options.port, options.host);
const browserServer = new import_utilsBundle.ws.EventEmitter();
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => device.close();
browserServer.kill = () => device.close();
device.on("close", () => {
server.close();
browserServer.emit("close");
});
return browserServer;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AndroidServerLauncherImpl
});

View File

@@ -1,88 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browserServerImpl_exports = {};
__export(browserServerImpl_exports, {
BrowserServerLauncherImpl: () => BrowserServerLauncherImpl
});
module.exports = __toCommonJS(browserServerImpl_exports);
var import_socksProxy = require("./server/utils/socksProxy");
var import_playwrightServer = require("./remote/playwrightServer");
var import_helper = require("./server/helper");
var import_instrumentation = require("./server/instrumentation");
var import_playwright = require("./server/playwright");
var import_crypto = require("./server/utils/crypto");
var import_stackTrace = require("./utils/isomorphic/stackTrace");
var import_utilsBundle = require("./utilsBundle");
class BrowserServerLauncherImpl {
constructor(browserName) {
this._browserName = browserName;
}
async launchServer(options = {}) {
const playwright = (0, import_playwright.createPlaywright)({ sdkLanguage: "javascript", isServer: true });
const socksProxy = false ? new SocksProxy() : void 0;
playwright.options.socksProxyPort = await socksProxy?.listen(0);
const metadata = (0, import_instrumentation.serverSideCallMetadata)();
const browser = await playwright[this._browserName].launch(metadata, {
...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? envObjectToArray(options.env) : void 0
}, toProtocolLogger(options.logger)).catch((e) => {
const log = import_helper.helper.formatBrowserLogs(metadata.log);
(0, import_stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`);
throw e;
});
const path = options.wsPath ? options.wsPath.startsWith("/") ? options.wsPath : `/${options.wsPath}` : `/${(0, import_crypto.createGuid)()}`;
const server = new import_playwrightServer.PlaywrightServer({ mode: "launchServer", path, maxConnections: Infinity, preLaunchedBrowser: browser, preLaunchedSocksProxy: socksProxy });
const wsEndpoint = await server.listen(options.port, options.host);
const browserServer = new import_utilsBundle.ws.EventEmitter();
browserServer.process = () => browser.options.browserProcess.process;
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => browser.options.browserProcess.close();
browserServer[Symbol.asyncDispose] = browserServer.close;
browserServer.kill = () => browser.options.browserProcess.kill();
browserServer._disconnectForTest = () => server.close();
browserServer._userDataDirForTest = browser._userDataDirForTest;
browser.options.browserProcess.onclose = (exitCode, signal) => {
socksProxy?.close().catch(() => {
});
server.close();
browserServer.emit("close", exitCode, signal);
};
return browserServer;
}
}
function toProtocolLogger(logger) {
return logger ? (direction, message) => {
if (logger.isEnabled("protocol", "verbose"))
logger.log("protocol", "verbose", (direction === "send" ? "SEND \u25BA " : "\u25C0 RECV ") + JSON.stringify(message), [], {});
} : void 0;
}
function envObjectToArray(env) {
const result = [];
for (const name in env) {
if (!Object.is(env[name], void 0))
result.push({ name, value: String(env[name]) });
}
return result;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BrowserServerLauncherImpl
});

View File

@@ -1,97 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var driver_exports = {};
__export(driver_exports, {
launchBrowserServer: () => launchBrowserServer,
printApiJson: () => printApiJson,
runDriver: () => runDriver,
runServer: () => runServer
});
module.exports = __toCommonJS(driver_exports);
var import_fs = __toESM(require("fs"));
var playwright = __toESM(require("../.."));
var import_pipeTransport = require("../server/utils/pipeTransport");
var import_playwrightServer = require("../remote/playwrightServer");
var import_server = require("../server");
var import_processLauncher = require("../server/utils/processLauncher");
function printApiJson() {
console.log(JSON.stringify(require("../../api.json")));
}
function runDriver() {
const dispatcherConnection = new import_server.DispatcherConnection();
new import_server.RootDispatcher(dispatcherConnection, async (rootScope, { sdkLanguage }) => {
const playwright2 = (0, import_server.createPlaywright)({ sdkLanguage });
return new import_server.PlaywrightDispatcher(rootScope, playwright2);
});
const transport = new import_pipeTransport.PipeTransport(process.stdout, process.stdin);
transport.onmessage = (message) => dispatcherConnection.dispatch(JSON.parse(message));
const isJavaScriptLanguageBinding = !process.env.PW_LANG_NAME || process.env.PW_LANG_NAME === "javascript";
const replacer = !isJavaScriptLanguageBinding && String.prototype.toWellFormed ? (key, value) => {
if (typeof value === "string")
return value.toWellFormed();
return value;
} : void 0;
dispatcherConnection.onmessage = (message) => transport.send(JSON.stringify(message, replacer));
transport.onclose = () => {
dispatcherConnection.onmessage = () => {
};
(0, import_processLauncher.gracefullyProcessExitDoNotHang)(0);
};
process.on("SIGINT", () => {
});
}
async function runServer(options) {
const {
port,
host,
path = "/",
maxConnections = Infinity,
extension
} = options;
const server = new import_playwrightServer.PlaywrightServer({ mode: extension ? "extension" : "default", path, maxConnections });
const wsEndpoint = await server.listen(port, host);
process.on("exit", () => server.close().catch(console.error));
console.log("Listening on " + wsEndpoint);
process.stdin.on("close", () => (0, import_processLauncher.gracefullyProcessExitDoNotHang)(0));
}
async function launchBrowserServer(browserName, configFile) {
let options = {};
if (configFile)
options = JSON.parse(import_fs.default.readFileSync(configFile).toString());
const browserType = playwright[browserName];
const server = await browserType.launchServer(options);
console.log(server.wsEndpoint());
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
launchBrowserServer,
printApiJson,
runDriver,
runServer
});

View File

@@ -1,567 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var program_exports = {};
__export(program_exports, {
program: () => import_utilsBundle2.program
});
module.exports = __toCommonJS(program_exports);
var import_fs = __toESM(require("fs"));
var import_os = __toESM(require("os"));
var import_path = __toESM(require("path"));
var playwright = __toESM(require("../.."));
var import_driver = require("./driver");
var import_server = require("../server");
var import_utils = require("../utils");
var import_traceViewer = require("../server/trace/viewer/traceViewer");
var import_utils2 = require("../utils");
var import_ascii = require("../server/utils/ascii");
var import_utilsBundle = require("../utilsBundle");
var import_utilsBundle2 = require("../utilsBundle");
const packageJSON = require("../../package.json");
import_utilsBundle.program.version("Version " + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME));
import_utilsBundle.program.command("mark-docker-image [dockerImageNameTemplate]", { hidden: true }).description("mark docker image").allowUnknownOption(true).action(function(dockerImageNameTemplate) {
(0, import_utils2.assert)(dockerImageNameTemplate, "dockerImageNameTemplate is required");
(0, import_server.writeDockerVersion)(dockerImageNameTemplate).catch(logErrorAndExit);
});
commandWithOpenOptions("open [url]", "open page in browser specified via -b, --browser", []).action(function(url, options) {
open(options, url, codegenId()).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ open
$ open -b webkit https://example.com`);
commandWithOpenOptions(
"codegen [url]",
"open page and generate code for user actions",
[
["-o, --output <file name>", "saves the generated script to a file"],
["--target <language>", `language to generate, one of javascript, playwright-test, python, python-async, python-pytest, csharp, csharp-mstest, csharp-nunit, java, java-junit`, codegenId()],
["--test-id-attribute <attributeName>", "use the specified attribute to generate data test ID selectors"]
]
).action(function(url, options) {
codegen(options, url).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ codegen
$ codegen --target=python
$ codegen -b webkit https://example.com`);
function suggestedBrowsersToInstall() {
return import_server.registry.executables().filter((e) => e.installType !== "none" && e.type !== "tool").map((e) => e.name).join(", ");
}
function defaultBrowsersToInstall(options) {
let executables = import_server.registry.defaultExecutables();
if (options.noShell)
executables = executables.filter((e) => e.name !== "chromium-headless-shell");
if (options.onlyShell)
executables = executables.filter((e) => e.name !== "chromium");
return executables;
}
function checkBrowsersToInstall(args, options) {
if (options.noShell && options.onlyShell)
throw new Error(`Only one of --no-shell and --only-shell can be specified`);
const faultyArguments = [];
const executables = [];
const handleArgument = (arg) => {
const executable = import_server.registry.findExecutable(arg);
if (!executable || executable.installType === "none")
faultyArguments.push(arg);
else
executables.push(executable);
if (executable?.browserName === "chromium")
executables.push(import_server.registry.findExecutable("ffmpeg"));
};
for (const arg of args) {
if (arg === "chromium") {
if (!options.onlyShell)
handleArgument("chromium");
if (!options.noShell)
handleArgument("chromium-headless-shell");
} else {
handleArgument(arg);
}
}
if (process.platform === "win32")
executables.push(import_server.registry.findExecutable("winldd"));
if (faultyArguments.length)
throw new Error(`Invalid installation targets: ${faultyArguments.map((name) => `'${name}'`).join(", ")}. Expecting one of: ${suggestedBrowsersToInstall()}`);
return executables;
}
import_utilsBundle.program.command("install [browser...]").description("ensure browsers necessary for this version of Playwright are installed").option("--with-deps", "install system dependencies for browsers").option("--dry-run", "do not execute installation, only print information").option("--force", "force reinstall of stable browser channels").option("--only-shell", "only install headless shell when installing chromium").option("--no-shell", "do not install chromium headless shell").action(async function(args, options) {
if (options.shell === false)
options.noShell = true;
if ((0, import_utils.isLikelyNpxGlobal)()) {
console.error((0, import_ascii.wrapInASCIIBox)([
`WARNING: It looks like you are running 'npx playwright install' without first`,
`installing your project's dependencies.`,
``,
`To avoid unexpected behavior, please install your dependencies first, and`,
`then run Playwright's install command:`,
``,
` npm install`,
` npx playwright install`,
``,
`If your project does not yet depend on Playwright, first install the`,
`applicable npm package (most commonly @playwright/test), and`,
`then run Playwright's install command to download the browsers:`,
``,
` npm install @playwright/test`,
` npx playwright install`,
``
].join("\n"), 1));
}
try {
const hasNoArguments = !args.length;
const executables = hasNoArguments ? defaultBrowsersToInstall(options) : checkBrowsersToInstall(args, options);
if (options.withDeps)
await import_server.registry.installDeps(executables, !!options.dryRun);
if (options.dryRun) {
for (const executable of executables) {
const version = executable.browserVersion ? `version ` + executable.browserVersion : "";
console.log(`browser: ${executable.name}${version ? " " + version : ""}`);
console.log(` Install location: ${executable.directory ?? "<system>"}`);
if (executable.downloadURLs?.length) {
const [url, ...fallbacks] = executable.downloadURLs;
console.log(` Download url: ${url}`);
for (let i = 0; i < fallbacks.length; ++i)
console.log(` Download fallback ${i + 1}: ${fallbacks[i]}`);
}
console.log(``);
}
} else {
const forceReinstall = hasNoArguments ? false : !!options.force;
await import_server.registry.install(executables, forceReinstall);
await import_server.registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || "javascript").catch((e) => {
e.name = "Playwright Host validation warning";
console.error(e);
});
}
} catch (e) {
console.log(`Failed to install browsers
${e}`);
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
}).addHelpText("afterAll", `
Examples:
- $ install
Install default browsers.
- $ install chrome firefox
Install custom browsers, supports ${suggestedBrowsersToInstall()}.`);
import_utilsBundle.program.command("uninstall").description("Removes browsers used by this installation of Playwright from the system (chromium, firefox, webkit, ffmpeg). This does not include branded channels.").option("--all", "Removes all browsers used by any Playwright installation from the system.").action(async (options) => {
delete process.env.PLAYWRIGHT_SKIP_BROWSER_GC;
await import_server.registry.uninstall(!!options.all).then(({ numberOfBrowsersLeft }) => {
if (!options.all && numberOfBrowsersLeft > 0) {
console.log("Successfully uninstalled Playwright browsers for the current Playwright installation.");
console.log(`There are still ${numberOfBrowsersLeft} browsers left, used by other Playwright installations.
To uninstall Playwright browsers for all installations, re-run with --all flag.`);
}
}).catch(logErrorAndExit);
});
import_utilsBundle.program.command("install-deps [browser...]").description("install dependencies necessary to run browsers (will ask for sudo permissions)").option("--dry-run", "Do not execute installation commands, only print them").action(async function(args, options) {
try {
if (!args.length)
await import_server.registry.installDeps(defaultBrowsersToInstall({}), !!options.dryRun);
else
await import_server.registry.installDeps(checkBrowsersToInstall(args, {}), !!options.dryRun);
} catch (e) {
console.log(`Failed to install browser dependencies
${e}`);
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
}).addHelpText("afterAll", `
Examples:
- $ install-deps
Install dependencies for default browsers.
- $ install-deps chrome firefox
Install dependencies for specific browsers, supports ${suggestedBrowsersToInstall()}.`);
const browsers = [
{ alias: "cr", name: "Chromium", type: "chromium" },
{ alias: "ff", name: "Firefox", type: "firefox" },
{ alias: "wk", name: "WebKit", type: "webkit" }
];
for (const { alias, name, type } of browsers) {
commandWithOpenOptions(`${alias} [url]`, `open page in ${name}`, []).action(function(url, options) {
open({ ...options, browser: type }, url, options.target).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ ${alias} https://example.com`);
}
commandWithOpenOptions(
"screenshot <url> <filename>",
"capture a page screenshot",
[
["--wait-for-selector <selector>", "wait for selector before taking a screenshot"],
["--wait-for-timeout <timeout>", "wait for timeout in milliseconds before taking a screenshot"],
["--full-page", "whether to take a full page screenshot (entire scrollable area)"]
]
).action(function(url, filename, command) {
screenshot(command, command, url, filename).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ screenshot -b webkit https://example.com example.png`);
commandWithOpenOptions(
"pdf <url> <filename>",
"save page as pdf",
[
["--paper-format <format>", "paper format: Letter, Legal, Tabloid, Ledger, A0, A1, A2, A3, A4, A5, A6"],
["--wait-for-selector <selector>", "wait for given selector before saving as pdf"],
["--wait-for-timeout <timeout>", "wait for given timeout in milliseconds before saving as pdf"]
]
).action(function(url, filename, options) {
pdf(options, options, url, filename).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ pdf https://example.com example.pdf`);
import_utilsBundle.program.command("run-driver", { hidden: true }).action(function(options) {
(0, import_driver.runDriver)();
});
import_utilsBundle.program.command("run-server").option("--port <port>", "Server port").option("--host <host>", "Server host").option("--path <path>", "Endpoint Path", "/").option("--max-clients <maxClients>", "Maximum clients").option("--mode <mode>", 'Server mode, either "default" or "extension"').action(function(options) {
(0, import_driver.runServer)({
port: options.port ? +options.port : void 0,
host: options.host,
path: options.path,
maxConnections: options.maxClients ? +options.maxClients : Infinity,
extension: options.mode === "extension" || !!process.env.PW_EXTENSION_MODE
}).catch(logErrorAndExit);
});
import_utilsBundle.program.command("print-api-json", { hidden: true }).action(function(options) {
(0, import_driver.printApiJson)();
});
import_utilsBundle.program.command("launch-server", { hidden: true }).requiredOption("--browser <browserName>", 'Browser name, one of "chromium", "firefox" or "webkit"').option("--config <path-to-config-file>", "JSON file with launchServer options").action(function(options) {
(0, import_driver.launchBrowserServer)(options.browser, options.config);
});
import_utilsBundle.program.command("show-trace [trace...]").option("-b, --browser <browserType>", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("-h, --host <host>", "Host to serve trace on; specifying this option opens trace in a browser tab").option("-p, --port <port>", "Port to serve trace on, 0 for any free port; specifying this option opens trace in a browser tab").option("--stdin", "Accept trace URLs over stdin to update the viewer").description("show trace viewer").action(function(traces, options) {
if (options.browser === "cr")
options.browser = "chromium";
if (options.browser === "ff")
options.browser = "firefox";
if (options.browser === "wk")
options.browser = "webkit";
const openOptions = {
host: options.host,
port: +options.port,
isServer: !!options.stdin
};
if (options.port !== void 0 || options.host !== void 0)
(0, import_traceViewer.runTraceInBrowser)(traces, openOptions).catch(logErrorAndExit);
else
(0, import_traceViewer.runTraceViewerApp)(traces, options.browser, openOptions, true).catch(logErrorAndExit);
}).addHelpText("afterAll", `
Examples:
$ show-trace https://example.com/trace.zip`);
async function launchContext(options, extraOptions) {
validateOptions(options);
const browserType = lookupBrowserType(options);
const launchOptions = extraOptions;
if (options.channel)
launchOptions.channel = options.channel;
launchOptions.handleSIGINT = false;
const contextOptions = (
// Copy the device descriptor since we have to compare and modify the options.
options.device ? { ...playwright.devices[options.device] } : {}
);
if (!extraOptions.headless)
contextOptions.deviceScaleFactor = import_os.default.platform() === "darwin" ? 2 : 1;
if (browserType.name() === "webkit" && process.platform === "linux") {
delete contextOptions.hasTouch;
delete contextOptions.isMobile;
}
if (contextOptions.isMobile && browserType.name() === "firefox")
contextOptions.isMobile = void 0;
if (options.blockServiceWorkers)
contextOptions.serviceWorkers = "block";
if (options.proxyServer) {
launchOptions.proxy = {
server: options.proxyServer
};
if (options.proxyBypass)
launchOptions.proxy.bypass = options.proxyBypass;
}
const browser = await browserType.launch(launchOptions);
if (process.env.PWTEST_CLI_IS_UNDER_TEST) {
process._didSetSourcesForTest = (text) => {
process.stdout.write("\n-------------8<-------------\n");
process.stdout.write(text);
process.stdout.write("\n-------------8<-------------\n");
const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
if (autoExitCondition && text.includes(autoExitCondition))
closeBrowser();
};
const logs = [];
require("playwright-core/lib/utilsBundle").debug.log = (...args) => {
const line = require("util").format(...args) + "\n";
logs.push(line);
process.stderr.write(line);
};
browser.on("disconnected", () => {
const hasCrashLine = logs.some((line) => line.includes("process did exit:") && !line.includes("process did exit: exitCode=0, signal=null"));
if (hasCrashLine) {
process.stderr.write("Detected browser crash.\n");
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
});
}
if (options.viewportSize) {
try {
const [width, height] = options.viewportSize.split(",").map((n) => +n);
if (isNaN(width) || isNaN(height))
throw new Error("bad values");
contextOptions.viewport = { width, height };
} catch (e) {
throw new Error('Invalid viewport size format: use "width,height", for example --viewport-size="800,600"');
}
}
if (options.geolocation) {
try {
const [latitude, longitude] = options.geolocation.split(",").map((n) => parseFloat(n.trim()));
contextOptions.geolocation = {
latitude,
longitude
};
} catch (e) {
throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"');
}
contextOptions.permissions = ["geolocation"];
}
if (options.userAgent)
contextOptions.userAgent = options.userAgent;
if (options.lang)
contextOptions.locale = options.lang;
if (options.colorScheme)
contextOptions.colorScheme = options.colorScheme;
if (options.timezone)
contextOptions.timezoneId = options.timezone;
if (options.loadStorage)
contextOptions.storageState = options.loadStorage;
if (options.ignoreHttpsErrors)
contextOptions.ignoreHTTPSErrors = true;
if (options.saveHar) {
contextOptions.recordHar = { path: import_path.default.resolve(process.cwd(), options.saveHar), mode: "minimal" };
if (options.saveHarGlob)
contextOptions.recordHar.urlFilter = options.saveHarGlob;
contextOptions.serviceWorkers = "block";
}
const context = await browser.newContext(contextOptions);
let closingBrowser = false;
async function closeBrowser() {
if (closingBrowser)
return;
closingBrowser = true;
if (options.saveStorage)
await context.storageState({ path: options.saveStorage }).catch((e) => null);
if (options.saveHar)
await context.close();
await browser.close();
}
context.on("page", (page) => {
page.on("dialog", () => {
});
page.on("close", () => {
const hasPage = browser.contexts().some((context2) => context2.pages().length > 0);
if (hasPage)
return;
closeBrowser().catch(() => {
});
});
});
process.on("SIGINT", async () => {
await closeBrowser();
(0, import_utils.gracefullyProcessExitDoNotHang)(130);
});
const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
context.setDefaultTimeout(timeout);
context.setDefaultNavigationTimeout(timeout);
delete launchOptions.headless;
delete launchOptions.executablePath;
delete launchOptions.handleSIGINT;
delete contextOptions.deviceScaleFactor;
return { browser, browserName: browserType.name(), context, contextOptions, launchOptions };
}
async function openPage(context, url) {
const page = await context.newPage();
if (url) {
if (import_fs.default.existsSync(url))
url = "file://" + import_path.default.resolve(url);
else if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:") && !url.startsWith("data:"))
url = "http://" + url;
await page.goto(url).catch((error) => {
if (process.env.PWTEST_CLI_AUTO_EXIT_WHEN) {
} else {
throw error;
}
});
}
return page;
}
async function open(options, url, language) {
const { context, launchOptions, contextOptions } = await launchContext(options, { headless: !!process.env.PWTEST_CLI_HEADLESS, executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH });
await context._enableRecorder({
language,
launchOptions,
contextOptions,
device: options.device,
saveStorage: options.saveStorage,
handleSIGINT: false
});
await openPage(context, url);
}
async function codegen(options, url) {
const { target: language, output: outputFile, testIdAttribute: testIdAttributeName } = options;
const tracesDir = import_path.default.join(import_os.default.tmpdir(), `playwright-recorder-trace-${Date.now()}`);
const { context, launchOptions, contextOptions } = await launchContext(options, {
headless: !!process.env.PWTEST_CLI_HEADLESS,
executablePath: process.env.PWTEST_CLI_EXECUTABLE_PATH,
tracesDir
});
import_utilsBundle.dotenv.config({ path: "playwright.env" });
await context._enableRecorder({
language,
launchOptions,
contextOptions,
device: options.device,
saveStorage: options.saveStorage,
mode: "recording",
testIdAttributeName,
outputFile: outputFile ? import_path.default.resolve(outputFile) : void 0,
handleSIGINT: false
});
await openPage(context, url);
}
async function waitForPage(page, captureOptions) {
if (captureOptions.waitForSelector) {
console.log(`Waiting for selector ${captureOptions.waitForSelector}...`);
await page.waitForSelector(captureOptions.waitForSelector);
}
if (captureOptions.waitForTimeout) {
console.log(`Waiting for timeout ${captureOptions.waitForTimeout}...`);
await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10));
}
}
async function screenshot(options, captureOptions, url, path2) {
const { context } = await launchContext(options, { headless: true });
console.log("Navigating to " + url);
const page = await openPage(context, url);
await waitForPage(page, captureOptions);
console.log("Capturing screenshot into " + path2);
await page.screenshot({ path: path2, fullPage: !!captureOptions.fullPage });
await page.close();
}
async function pdf(options, captureOptions, url, path2) {
if (options.browser !== "chromium")
throw new Error("PDF creation is only working with Chromium");
const { context } = await launchContext({ ...options, browser: "chromium" }, { headless: true });
console.log("Navigating to " + url);
const page = await openPage(context, url);
await waitForPage(page, captureOptions);
console.log("Saving as pdf into " + path2);
await page.pdf({ path: path2, format: captureOptions.paperFormat });
await page.close();
}
function lookupBrowserType(options) {
let name = options.browser;
if (options.device) {
const device = playwright.devices[options.device];
name = device.defaultBrowserType;
}
let browserType;
switch (name) {
case "chromium":
browserType = playwright.chromium;
break;
case "webkit":
browserType = playwright.webkit;
break;
case "firefox":
browserType = playwright.firefox;
break;
case "cr":
browserType = playwright.chromium;
break;
case "wk":
browserType = playwright.webkit;
break;
case "ff":
browserType = playwright.firefox;
break;
}
if (browserType)
return browserType;
import_utilsBundle.program.help();
}
function validateOptions(options) {
if (options.device && !(options.device in playwright.devices)) {
const lines = [`Device descriptor not found: '${options.device}', available devices are:`];
for (const name in playwright.devices)
lines.push(` "${name}"`);
throw new Error(lines.join("\n"));
}
if (options.colorScheme && !["light", "dark"].includes(options.colorScheme))
throw new Error('Invalid color scheme, should be one of "light", "dark"');
}
function logErrorAndExit(e) {
if (process.env.PWDEBUGIMPL)
console.error(e);
else
console.error(e.name + ": " + e.message);
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
function codegenId() {
return process.env.PW_LANG_NAME || "playwright-test";
}
function commandWithOpenOptions(command, description, options) {
let result = import_utilsBundle.program.command(command).description(description);
for (const option of options)
result = result.option(option[0], ...option.slice(1));
return result.option("-b, --browser <browserType>", "browser to use, one of cr, chromium, ff, firefox, wk, webkit", "chromium").option("--block-service-workers", "block service workers").option("--channel <channel>", 'Chromium distribution channel, "chrome", "chrome-beta", "msedge-dev", etc').option("--color-scheme <scheme>", 'emulate preferred color scheme, "light" or "dark"').option("--device <deviceName>", 'emulate device, for example "iPhone 11"').option("--geolocation <coordinates>", 'specify geolocation coordinates, for example "37.819722,-122.478611"').option("--ignore-https-errors", "ignore https errors").option("--load-storage <filename>", "load context storage state from the file, previously saved with --save-storage").option("--lang <language>", 'specify language / locale, for example "en-GB"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--save-har <filename>", "save HAR file with all network activity at the end").option("--save-har-glob <glob pattern>", "filter entries in the HAR by matching url against this glob pattern").option("--save-storage <filename>", "save context storage state at the end, for later use with --load-storage").option("--timezone <time zone>", 'time zone to emulate, for example "Europe/Rome"').option("--timeout <timeout>", "timeout for Playwright actions in milliseconds, no timeout by default").option("--user-agent <ua string>", "specify user agent string").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"');
}
function buildBasePlaywrightCLICommand(cliTargetLang) {
switch (cliTargetLang) {
case "python":
return `playwright`;
case "java":
return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="...options.."`;
case "csharp":
return `pwsh bin/Debug/netX/playwright.ps1`;
default: {
const packageManagerCommand = (0, import_utils2.getPackageManagerExecCommand)();
return `${packageManagerCommand} playwright`;
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
program
});

View File

@@ -1,74 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var programWithTestStub_exports = {};
__export(programWithTestStub_exports, {
program: () => import_program2.program
});
module.exports = __toCommonJS(programWithTestStub_exports);
var import_processLauncher = require("../server/utils/processLauncher");
var import_utils = require("../utils");
var import_program = require("./program");
var import_program2 = require("./program");
function printPlaywrightTestError(command) {
const packages = [];
for (const pkg of ["playwright", "playwright-chromium", "playwright-firefox", "playwright-webkit"]) {
try {
require.resolve(pkg);
packages.push(pkg);
} catch (e) {
}
}
if (!packages.length)
packages.push("playwright");
const packageManager = (0, import_utils.getPackageManager)();
if (packageManager === "yarn") {
console.error(`Please install @playwright/test package before running "yarn playwright ${command}"`);
console.error(` yarn remove ${packages.join(" ")}`);
console.error(" yarn add -D @playwright/test");
} else if (packageManager === "pnpm") {
console.error(`Please install @playwright/test package before running "pnpm exec playwright ${command}"`);
console.error(` pnpm remove ${packages.join(" ")}`);
console.error(" pnpm add -D @playwright/test");
} else {
console.error(`Please install @playwright/test package before running "npx playwright ${command}"`);
console.error(` npm uninstall ${packages.join(" ")}`);
console.error(" npm install -D @playwright/test");
}
}
const kExternalPlaywrightTestCommands = [
["test", "Run tests with Playwright Test."],
["show-report", "Show Playwright Test HTML report."],
["merge-reports", "Merge Playwright Test Blob reports"]
];
function addExternalPlaywrightTestCommands() {
for (const [command, description] of kExternalPlaywrightTestCommands) {
const playwrightTest = import_program.program.command(command).allowUnknownOption(true);
playwrightTest.description(`${description} Available in @playwright/test package.`);
playwrightTest.action(async () => {
printPlaywrightTestError(command);
(0, import_processLauncher.gracefullyProcessExitDoNotHang)(1);
});
}
}
if (!process.env.PW_LANG_NAME)
addExternalPlaywrightTestCommands();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
program
});

View File

@@ -1,49 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var accessibility_exports = {};
__export(accessibility_exports, {
Accessibility: () => Accessibility
});
module.exports = __toCommonJS(accessibility_exports);
function axNodeFromProtocol(axNode) {
const result = {
...axNode,
value: axNode.valueNumber !== void 0 ? axNode.valueNumber : axNode.valueString,
checked: axNode.checked === "checked" ? true : axNode.checked === "unchecked" ? false : axNode.checked,
pressed: axNode.pressed === "pressed" ? true : axNode.pressed === "released" ? false : axNode.pressed,
children: axNode.children ? axNode.children.map(axNodeFromProtocol) : void 0
};
delete result.valueNumber;
delete result.valueString;
return result;
}
class Accessibility {
constructor(channel) {
this._channel = channel;
}
async snapshot(options = {}) {
const root = options.root ? options.root._elementChannel : void 0;
const result = await this._channel.accessibilitySnapshot({ interestingOnly: options.interestingOnly, root });
return result.rootAXNode ? axNodeFromProtocol(result.rootAXNode) : null;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Accessibility
});

View File

@@ -1,365 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var android_exports = {};
__export(android_exports, {
Android: () => Android,
AndroidDevice: () => AndroidDevice,
AndroidInput: () => AndroidInput,
AndroidSocket: () => AndroidSocket,
AndroidWebView: () => AndroidWebView
});
module.exports = __toCommonJS(android_exports);
var import_eventEmitter = require("./eventEmitter");
var import_browserContext = require("./browserContext");
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_events = require("./events");
var import_waiter = require("./waiter");
var import_timeoutSettings = require("./timeoutSettings");
var import_rtti = require("../utils/isomorphic/rtti");
var import_time = require("../utils/isomorphic/time");
var import_timeoutRunner = require("../utils/isomorphic/timeoutRunner");
var import_webSocket = require("./webSocket");
class Android extends import_channelOwner.ChannelOwner {
static from(android) {
return android._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._wrapApiCall(async () => {
await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true).catch(() => {
});
}
async devices(options = {}) {
const { devices } = await this._channel.devices(options);
return devices.map((d) => AndroidDevice.from(d));
}
async launchServer(options = {}) {
if (!this._serverLauncher)
throw new Error("Launching server is not supported");
return await this._serverLauncher.launchServer(options);
}
async connect(wsEndpoint, options = {}) {
return await this._wrapApiCall(async () => {
const deadline = options.timeout ? (0, import_time.monotonicTime)() + options.timeout : 0;
const headers = { "x-playwright-browser": "android", ...options.headers };
const connectParams = { wsEndpoint, headers, slowMo: options.slowMo, timeout: options.timeout };
const connection = await (0, import_webSocket.connectOverWebSocket)(this._connection, connectParams);
let device;
connection.on("close", () => {
device?._didClose();
});
const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
const playwright = await connection.initializePlaywright();
if (!playwright._initializer.preConnectedAndroidDevice) {
connection.close();
throw new Error("Malformed endpoint. Did you use Android.launchServer method?");
}
device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice);
device._shouldCloseConnectionOnClose = true;
device.on(import_events.Events.AndroidDevice.Close, () => connection.close());
return device;
}, deadline);
if (!result.timedOut) {
return result.result;
} else {
connection.close();
throw new Error(`Timeout ${options.timeout}ms exceeded`);
}
});
}
}
class AndroidDevice extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._webViews = /* @__PURE__ */ new Map();
this._shouldCloseConnectionOnClose = false;
this.input = new AndroidInput(this);
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform, parent._timeoutSettings);
this._channel.on("webViewAdded", ({ webView }) => this._onWebViewAdded(webView));
this._channel.on("webViewRemoved", ({ socketName }) => this._onWebViewRemoved(socketName));
this._channel.on("close", () => this._didClose());
}
static from(androidDevice) {
return androidDevice._object;
}
_onWebViewAdded(webView) {
const view = new AndroidWebView(this, webView);
this._webViews.set(webView.socketName, view);
this.emit(import_events.Events.AndroidDevice.WebView, view);
}
_onWebViewRemoved(socketName) {
const view = this._webViews.get(socketName);
this._webViews.delete(socketName);
if (view)
view.emit(import_events.Events.AndroidWebView.Close);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._wrapApiCall(async () => {
await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true).catch(() => {
});
}
serial() {
return this._initializer.serial;
}
model() {
return this._initializer.model;
}
webViews() {
return [...this._webViews.values()];
}
async webView(selector, options) {
const predicate = (v) => {
if (selector.pkg)
return v.pkg() === selector.pkg;
if (selector.socketName)
return v._socketName() === selector.socketName;
return false;
};
const webView = [...this._webViews.values()].find(predicate);
if (webView)
return webView;
return await this.waitForEvent("webview", { ...options, predicate });
}
async wait(selector, options) {
await this._channel.wait({ selector: toSelectorChannel(selector), ...options });
}
async fill(selector, text, options) {
await this._channel.fill({ selector: toSelectorChannel(selector), text, ...options });
}
async press(selector, key, options) {
await this.tap(selector, options);
await this.input.press(key);
}
async tap(selector, options) {
await this._channel.tap({ selector: toSelectorChannel(selector), ...options });
}
async drag(selector, dest, options) {
await this._channel.drag({ selector: toSelectorChannel(selector), dest, ...options });
}
async fling(selector, direction, options) {
await this._channel.fling({ selector: toSelectorChannel(selector), direction, ...options });
}
async longTap(selector, options) {
await this._channel.longTap({ selector: toSelectorChannel(selector), ...options });
}
async pinchClose(selector, percent, options) {
await this._channel.pinchClose({ selector: toSelectorChannel(selector), percent, ...options });
}
async pinchOpen(selector, percent, options) {
await this._channel.pinchOpen({ selector: toSelectorChannel(selector), percent, ...options });
}
async scroll(selector, direction, percent, options) {
await this._channel.scroll({ selector: toSelectorChannel(selector), direction, percent, ...options });
}
async swipe(selector, direction, percent, options) {
await this._channel.swipe({ selector: toSelectorChannel(selector), direction, percent, ...options });
}
async info(selector) {
return (await this._channel.info({ selector: toSelectorChannel(selector) })).info;
}
async screenshot(options = {}) {
const { binary } = await this._channel.screenshot();
if (options.path)
await this._platform.fs().promises.writeFile(options.path, binary);
return binary;
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close() {
try {
if (this._shouldCloseConnectionOnClose)
this._connection.close();
else
await this._channel.close();
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
_didClose() {
this.emit(import_events.Events.AndroidDevice.Close, this);
}
async shell(command) {
const { result } = await this._channel.shell({ command });
return result;
}
async open(command) {
return AndroidSocket.from((await this._channel.open({ command })).socket);
}
async installApk(file, options) {
await this._channel.installApk({ file: await loadFile(this._platform, file), args: options && options.args });
}
async push(file, path, options) {
await this._channel.push({ file: await loadFile(this._platform, file), path, mode: options ? options.mode : void 0 });
}
async launchBrowser(options = {}) {
const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const result = await this._channel.launchBrowser(contextOptions);
const context = import_browserContext.BrowserContext.from(result.context);
context._setOptions(contextOptions, {});
return context;
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.AndroidDevice.Close)
waiter.rejectOnEvent(this, import_events.Events.AndroidDevice.Close, () => new import_errors.TargetClosedError());
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
}
class AndroidSocket extends import_channelOwner.ChannelOwner {
static from(androidDevice) {
return androidDevice._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._channel.on("data", ({ data }) => this.emit(import_events.Events.AndroidSocket.Data, data));
this._channel.on("close", () => this.emit(import_events.Events.AndroidSocket.Close));
}
async write(data) {
await this._channel.write({ data });
}
async close() {
await this._channel.close();
}
async [Symbol.asyncDispose]() {
await this.close();
}
}
async function loadFile(platform, file) {
if ((0, import_rtti.isString)(file))
return await platform.fs().promises.readFile(file);
return file;
}
class AndroidInput {
constructor(device) {
this._device = device;
}
async type(text) {
await this._device._channel.inputType({ text });
}
async press(key) {
await this._device._channel.inputPress({ key });
}
async tap(point) {
await this._device._channel.inputTap({ point });
}
async swipe(from, segments, steps) {
await this._device._channel.inputSwipe({ segments, steps });
}
async drag(from, to, steps) {
await this._device._channel.inputDrag({ from, to, steps });
}
}
function toSelectorChannel(selector) {
const {
checkable,
checked,
clazz,
clickable,
depth,
desc,
enabled,
focusable,
focused,
hasChild,
hasDescendant,
longClickable,
pkg,
res,
scrollable,
selected,
text
} = selector;
const toRegex = (value) => {
if (value === void 0)
return void 0;
if ((0, import_rtti.isRegExp)(value))
return value.source;
return "^" + value.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d") + "$";
};
return {
checkable,
checked,
clazz: toRegex(clazz),
pkg: toRegex(pkg),
desc: toRegex(desc),
res: toRegex(res),
text: toRegex(text),
clickable,
depth,
enabled,
focusable,
focused,
hasChild: hasChild ? { selector: toSelectorChannel(hasChild.selector) } : void 0,
hasDescendant: hasDescendant ? { selector: toSelectorChannel(hasDescendant.selector), maxDepth: hasDescendant.maxDepth } : void 0,
longClickable,
scrollable,
selected
};
}
class AndroidWebView extends import_eventEmitter.EventEmitter {
constructor(device, data) {
super(device._platform);
this._device = device;
this._data = data;
}
pid() {
return this._data.pid;
}
pkg() {
return this._data.pkg;
}
_socketName() {
return this._data.socketName;
}
async page() {
if (!this._pagePromise)
this._pagePromise = this._fetchPage();
return await this._pagePromise;
}
async _fetchPage() {
const { context } = await this._device._channel.connectToWebView({ socketName: this._data.socketName });
return import_browserContext.BrowserContext.from(context).pages()[0];
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Android,
AndroidDevice,
AndroidInput,
AndroidSocket,
AndroidWebView
});

View File

@@ -1,137 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var api_exports = {};
__export(api_exports, {
APIRequest: () => import_fetch.APIRequest,
APIRequestContext: () => import_fetch.APIRequestContext,
APIResponse: () => import_fetch.APIResponse,
Accessibility: () => import_accessibility.Accessibility,
Android: () => import_android.Android,
AndroidDevice: () => import_android.AndroidDevice,
AndroidInput: () => import_android.AndroidInput,
AndroidSocket: () => import_android.AndroidSocket,
AndroidWebView: () => import_android.AndroidWebView,
Browser: () => import_browser.Browser,
BrowserContext: () => import_browserContext.BrowserContext,
BrowserType: () => import_browserType.BrowserType,
CDPSession: () => import_cdpSession.CDPSession,
Clock: () => import_clock.Clock,
ConsoleMessage: () => import_consoleMessage.ConsoleMessage,
Coverage: () => import_coverage.Coverage,
Dialog: () => import_dialog.Dialog,
Download: () => import_download.Download,
Electron: () => import_electron.Electron,
ElectronApplication: () => import_electron.ElectronApplication,
ElementHandle: () => import_elementHandle.ElementHandle,
FileChooser: () => import_fileChooser.FileChooser,
Frame: () => import_frame.Frame,
FrameLocator: () => import_locator.FrameLocator,
JSHandle: () => import_jsHandle.JSHandle,
Keyboard: () => import_input.Keyboard,
Locator: () => import_locator.Locator,
Mouse: () => import_input.Mouse,
Page: () => import_page.Page,
Playwright: () => import_playwright.Playwright,
Request: () => import_network.Request,
Response: () => import_network.Response,
Route: () => import_network.Route,
Selectors: () => import_selectors.Selectors,
TimeoutError: () => import_errors.TimeoutError,
Touchscreen: () => import_input.Touchscreen,
Tracing: () => import_tracing.Tracing,
Video: () => import_video.Video,
WebError: () => import_webError.WebError,
WebSocket: () => import_network.WebSocket,
WebSocketRoute: () => import_network.WebSocketRoute,
Worker: () => import_worker.Worker
});
module.exports = __toCommonJS(api_exports);
var import_accessibility = require("./accessibility");
var import_android = require("./android");
var import_browser = require("./browser");
var import_browserContext = require("./browserContext");
var import_browserType = require("./browserType");
var import_clock = require("./clock");
var import_consoleMessage = require("./consoleMessage");
var import_coverage = require("./coverage");
var import_dialog = require("./dialog");
var import_download = require("./download");
var import_electron = require("./electron");
var import_locator = require("./locator");
var import_elementHandle = require("./elementHandle");
var import_fileChooser = require("./fileChooser");
var import_errors = require("./errors");
var import_frame = require("./frame");
var import_input = require("./input");
var import_jsHandle = require("./jsHandle");
var import_network = require("./network");
var import_fetch = require("./fetch");
var import_page = require("./page");
var import_selectors = require("./selectors");
var import_tracing = require("./tracing");
var import_video = require("./video");
var import_worker = require("./worker");
var import_cdpSession = require("./cdpSession");
var import_playwright = require("./playwright");
var import_webError = require("./webError");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
APIRequest,
APIRequestContext,
APIResponse,
Accessibility,
Android,
AndroidDevice,
AndroidInput,
AndroidSocket,
AndroidWebView,
Browser,
BrowserContext,
BrowserType,
CDPSession,
Clock,
ConsoleMessage,
Coverage,
Dialog,
Download,
Electron,
ElectronApplication,
ElementHandle,
FileChooser,
Frame,
FrameLocator,
JSHandle,
Keyboard,
Locator,
Mouse,
Page,
Playwright,
Request,
Response,
Route,
Selectors,
TimeoutError,
Touchscreen,
Tracing,
Video,
WebError,
WebSocket,
WebSocketRoute,
Worker
});

View File

@@ -1,79 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var artifact_exports = {};
__export(artifact_exports, {
Artifact: () => Artifact
});
module.exports = __toCommonJS(artifact_exports);
var import_channelOwner = require("./channelOwner");
var import_stream = require("./stream");
var import_fileUtils = require("./fileUtils");
class Artifact extends import_channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
async pathAfterFinished() {
if (this._connection.isRemote())
throw new Error(`Path is not available when connecting remotely. Use saveAs() to save a local copy.`);
return (await this._channel.pathAfterFinished()).value;
}
async saveAs(path) {
if (!this._connection.isRemote()) {
await this._channel.saveAs({ path });
return;
}
const result = await this._channel.saveAsStream();
const stream = import_stream.Stream.from(result.stream);
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, path);
await new Promise((resolve, reject) => {
stream.stream().pipe(this._platform.fs().createWriteStream(path)).on("finish", resolve).on("error", reject);
});
}
async failure() {
return (await this._channel.failure()).error || null;
}
async createReadStream() {
const result = await this._channel.stream();
const stream = import_stream.Stream.from(result.stream);
return stream.stream();
}
async readIntoBuffer() {
const stream = await this.createReadStream();
return await new Promise((resolve, reject) => {
const chunks = [];
stream.on("data", (chunk) => {
chunks.push(chunk);
});
stream.on("end", () => {
resolve(Buffer.concat(chunks));
});
stream.on("error", reject);
});
}
async cancel() {
return await this._channel.cancel();
}
async delete() {
return await this._channel.delete();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Artifact
});

View File

@@ -1,136 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browser_exports = {};
__export(browser_exports, {
Browser: () => Browser
});
module.exports = __toCommonJS(browser_exports);
var import_artifact = require("./artifact");
var import_browserContext = require("./browserContext");
var import_cdpSession = require("./cdpSession");
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_events = require("./events");
var import_fileUtils = require("./fileUtils");
class Browser extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._contexts = /* @__PURE__ */ new Set();
this._isConnected = true;
this._shouldCloseConnectionOnClose = false;
this._options = {};
this._name = initializer.name;
this._channel.on("close", () => this._didClose());
this._closedPromise = new Promise((f) => this.once(import_events.Events.Browser.Disconnected, f));
}
static from(browser) {
return browser._object;
}
browserType() {
return this._browserType;
}
async newContext(options = {}) {
return await this._innerNewContext(options, false);
}
async _newContextForReuse(options = {}) {
return await this._wrapApiCall(async () => {
for (const context of this._contexts) {
await this._browserType._willCloseContext(context);
for (const page of context.pages())
page._onClose();
context._onClose();
}
return await this._innerNewContext(options, true);
}, true);
}
async _stopPendingOperations(reason) {
return await this._wrapApiCall(async () => {
await this._channel.stopPendingOperations({ reason });
}, true);
}
async _innerNewContext(options = {}, forReuse) {
options = { ...this._browserType._playwright._defaultContextOptions, ...options };
const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const response = forReuse ? await this._channel.newContextForReuse(contextOptions) : await this._channel.newContext(contextOptions);
const context = import_browserContext.BrowserContext.from(response.context);
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
return context;
}
contexts() {
return [...this._contexts];
}
version() {
return this._initializer.version;
}
async newPage(options = {}) {
return await this._wrapApiCall(async () => {
const context = await this.newContext(options);
const page = await context.newPage();
page._ownedContext = context;
context._ownerPage = page;
return page;
});
}
isConnected() {
return this._isConnected;
}
async newBrowserCDPSession() {
return import_cdpSession.CDPSession.from((await this._channel.newBrowserCDPSession()).session);
}
async startTracing(page, options = {}) {
this._path = options.path;
await this._channel.startTracing({ ...options, page: page ? page._channel : void 0 });
}
async stopTracing() {
const artifact = import_artifact.Artifact.from((await this._channel.stopTracing()).artifact);
const buffer = await artifact.readIntoBuffer();
await artifact.delete();
if (this._path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, this._path);
await this._platform.fs().promises.writeFile(this._path, buffer);
this._path = void 0;
}
return buffer;
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close(options = {}) {
this._closeReason = options.reason;
try {
if (this._shouldCloseConnectionOnClose)
this._connection.close();
else
await this._channel.close(options);
await this._closedPromise;
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
_didClose() {
this._isConnected = false;
this.emit(import_events.Events.Browser.Disconnected, this);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Browser
});

View File

@@ -1,539 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browserContext_exports = {};
__export(browserContext_exports, {
BrowserContext: () => BrowserContext,
prepareBrowserContextParams: () => prepareBrowserContextParams,
toClientCertificatesProtocol: () => toClientCertificatesProtocol
});
module.exports = __toCommonJS(browserContext_exports);
var import_artifact = require("./artifact");
var import_browser = require("./browser");
var import_cdpSession = require("./cdpSession");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_clock = require("./clock");
var import_consoleMessage = require("./consoleMessage");
var import_dialog = require("./dialog");
var import_errors = require("./errors");
var import_events = require("./events");
var import_fetch = require("./fetch");
var import_frame = require("./frame");
var import_harRouter = require("./harRouter");
var network = __toESM(require("./network"));
var import_page = require("./page");
var import_tracing = require("./tracing");
var import_waiter = require("./waiter");
var import_webError = require("./webError");
var import_worker = require("./worker");
var import_timeoutSettings = require("./timeoutSettings");
var import_fileUtils = require("./fileUtils");
var import_headers = require("../utils/isomorphic/headers");
var import_urlMatch = require("../utils/isomorphic/urlMatch");
var import_rtti = require("../utils/isomorphic/rtti");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
class BrowserContext extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._pages = /* @__PURE__ */ new Set();
this._routes = [];
this._webSocketRoutes = [];
this._browser = null;
this._bindings = /* @__PURE__ */ new Map();
this._options = {};
this._backgroundPages = /* @__PURE__ */ new Set();
this._serviceWorkers = /* @__PURE__ */ new Set();
this._harRecorders = /* @__PURE__ */ new Map();
this._closeWasCalled = false;
this._harRouters = [];
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
if (parent instanceof import_browser.Browser)
this._browser = parent;
this._browser?._contexts.add(this);
this._isChromium = this._browser?._name === "chromium";
this.tracing = import_tracing.Tracing.from(initializer.tracing);
this.request = import_fetch.APIRequestContext.from(initializer.requestContext);
this.clock = new import_clock.Clock(this);
this._channel.on("bindingCall", ({ binding }) => this._onBinding(import_page.BindingCall.from(binding)));
this._channel.on("close", () => this._onClose());
this._channel.on("page", ({ page }) => this._onPage(import_page.Page.from(page)));
this._channel.on("route", ({ route }) => this._onRoute(network.Route.from(route)));
this._channel.on("webSocketRoute", ({ webSocketRoute }) => this._onWebSocketRoute(network.WebSocketRoute.from(webSocketRoute)));
this._channel.on("backgroundPage", ({ page }) => {
const backgroundPage = import_page.Page.from(page);
this._backgroundPages.add(backgroundPage);
this.emit(import_events.Events.BrowserContext.BackgroundPage, backgroundPage);
});
this._channel.on("serviceWorker", ({ worker }) => {
const serviceWorker = import_worker.Worker.from(worker);
serviceWorker._context = this;
this._serviceWorkers.add(serviceWorker);
this.emit(import_events.Events.BrowserContext.ServiceWorker, serviceWorker);
});
this._channel.on("console", (event) => {
const consoleMessage = new import_consoleMessage.ConsoleMessage(this._platform, event);
this.emit(import_events.Events.BrowserContext.Console, consoleMessage);
const page = consoleMessage.page();
if (page)
page.emit(import_events.Events.Page.Console, consoleMessage);
});
this._channel.on("pageError", ({ error, page }) => {
const pageObject = import_page.Page.from(page);
const parsedError = (0, import_errors.parseError)(error);
this.emit(import_events.Events.BrowserContext.WebError, new import_webError.WebError(pageObject, parsedError));
if (pageObject)
pageObject.emit(import_events.Events.Page.PageError, parsedError);
});
this._channel.on("dialog", ({ dialog }) => {
const dialogObject = import_dialog.Dialog.from(dialog);
let hasListeners = this.emit(import_events.Events.BrowserContext.Dialog, dialogObject);
const page = dialogObject.page();
if (page)
hasListeners = page.emit(import_events.Events.Page.Dialog, dialogObject) || hasListeners;
if (!hasListeners) {
if (dialogObject.type() === "beforeunload")
dialog.accept({}).catch(() => {
});
else
dialog.dismiss().catch(() => {
});
}
});
this._channel.on("request", ({ request, page }) => this._onRequest(network.Request.from(request), import_page.Page.fromNullable(page)));
this._channel.on("requestFailed", ({ request, failureText, responseEndTiming, page }) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, import_page.Page.fromNullable(page)));
this._channel.on("requestFinished", (params) => this._onRequestFinished(params));
this._channel.on("response", ({ response, page }) => this._onResponse(network.Response.from(response), import_page.Page.fromNullable(page)));
this._closedPromise = new Promise((f) => this.once(import_events.Events.BrowserContext.Close, f));
this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
[import_events.Events.BrowserContext.Console, "console"],
[import_events.Events.BrowserContext.Dialog, "dialog"],
[import_events.Events.BrowserContext.Request, "request"],
[import_events.Events.BrowserContext.Response, "response"],
[import_events.Events.BrowserContext.RequestFinished, "requestFinished"],
[import_events.Events.BrowserContext.RequestFailed, "requestFailed"]
]));
}
static from(context) {
return context._object;
}
static fromNullable(context) {
return context ? BrowserContext.from(context) : null;
}
_setOptions(contextOptions, browserOptions) {
this._options = contextOptions;
if (this._options.recordHar)
this._harRecorders.set("", { path: this._options.recordHar.path, content: this._options.recordHar.content });
this.tracing._tracesDir = browserOptions.tracesDir;
}
_onPage(page) {
this._pages.add(page);
this.emit(import_events.Events.BrowserContext.Page, page);
if (page._opener && !page._opener.isClosed())
page._opener.emit(import_events.Events.Page.Popup, page);
}
_onRequest(request, page) {
this.emit(import_events.Events.BrowserContext.Request, request);
if (page)
page.emit(import_events.Events.Page.Request, request);
}
_onResponse(response, page) {
this.emit(import_events.Events.BrowserContext.Response, response);
if (page)
page.emit(import_events.Events.Page.Response, response);
}
_onRequestFailed(request, responseEndTiming, failureText, page) {
request._failureText = failureText || null;
request._setResponseEndTiming(responseEndTiming);
this.emit(import_events.Events.BrowserContext.RequestFailed, request);
if (page)
page.emit(import_events.Events.Page.RequestFailed, request);
}
_onRequestFinished(params) {
const { responseEndTiming } = params;
const request = network.Request.from(params.request);
const response = network.Response.fromNullable(params.response);
const page = import_page.Page.fromNullable(params.page);
request._setResponseEndTiming(responseEndTiming);
this.emit(import_events.Events.BrowserContext.RequestFinished, request);
if (page)
page.emit(import_events.Events.Page.RequestFinished, request);
if (response)
response._finishedPromise.resolve(null);
}
async _onRoute(route) {
route._context = this;
const page = route.request()._safePage();
const routeHandlers = this._routes.slice();
for (const routeHandler of routeHandlers) {
if (page?._closeWasCalled || this._closeWasCalled)
return;
if (!routeHandler.matches(route.request().url()))
continue;
const index = this._routes.indexOf(routeHandler);
if (index === -1)
continue;
if (routeHandler.willExpire())
this._routes.splice(index, 1);
const handled = await routeHandler.handle(route);
if (!this._routes.length)
this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {
});
if (handled)
return;
}
await route._innerContinue(
true
/* isFallback */
).catch(() => {
});
}
async _onWebSocketRoute(webSocketRoute) {
const routeHandler = this._webSocketRoutes.find((route) => route.matches(webSocketRoute.url()));
if (routeHandler)
await routeHandler.handle(webSocketRoute);
else
webSocketRoute.connectToServer();
}
async _onBinding(bindingCall) {
const func = this._bindings.get(bindingCall._initializer.name);
if (!func)
return;
await bindingCall.call(func);
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
this._wrapApiCall(async () => {
await this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
}, true).catch(() => {
});
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._wrapApiCall(async () => {
await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true).catch(() => {
});
}
browser() {
return this._browser;
}
pages() {
return [...this._pages];
}
async newPage() {
if (this._ownerPage)
throw new Error("Please use browser.newContext()");
return import_page.Page.from((await this._channel.newPage()).page);
}
async cookies(urls) {
if (!urls)
urls = [];
if (urls && typeof urls === "string")
urls = [urls];
return (await this._channel.cookies({ urls })).cookies;
}
async addCookies(cookies) {
await this._channel.addCookies({ cookies });
}
async clearCookies(options = {}) {
await this._channel.clearCookies({
name: (0, import_rtti.isString)(options.name) ? options.name : void 0,
nameRegexSource: (0, import_rtti.isRegExp)(options.name) ? options.name.source : void 0,
nameRegexFlags: (0, import_rtti.isRegExp)(options.name) ? options.name.flags : void 0,
domain: (0, import_rtti.isString)(options.domain) ? options.domain : void 0,
domainRegexSource: (0, import_rtti.isRegExp)(options.domain) ? options.domain.source : void 0,
domainRegexFlags: (0, import_rtti.isRegExp)(options.domain) ? options.domain.flags : void 0,
path: (0, import_rtti.isString)(options.path) ? options.path : void 0,
pathRegexSource: (0, import_rtti.isRegExp)(options.path) ? options.path.source : void 0,
pathRegexFlags: (0, import_rtti.isRegExp)(options.path) ? options.path.flags : void 0
});
}
async grantPermissions(permissions, options) {
await this._channel.grantPermissions({ permissions, ...options });
}
async clearPermissions() {
await this._channel.clearPermissions();
}
async setGeolocation(geolocation) {
await this._channel.setGeolocation({ geolocation: geolocation || void 0 });
}
async setExtraHTTPHeaders(headers) {
network.validateHeaders(headers);
await this._channel.setExtraHTTPHeaders({ headers: (0, import_headers.headersObjectToArray)(headers) });
}
async setOffline(offline) {
await this._channel.setOffline({ offline });
}
async setHTTPCredentials(httpCredentials) {
await this._channel.setHTTPCredentials({ httpCredentials: httpCredentials || void 0 });
}
async addInitScript(script, arg) {
const source = await (0, import_clientHelper.evaluationScript)(this._platform, script, arg);
await this._channel.addInitScript({ source });
}
async exposeBinding(name, callback, options = {}) {
await this._channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, callback);
}
async exposeFunction(name, callback) {
await this._channel.exposeBinding({ name });
const binding = (source, ...args) => callback(...args);
this._bindings.set(name, binding);
}
async route(url, handler, options = {}) {
this._routes.unshift(new network.RouteHandler(this._platform, this._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns();
}
async routeWebSocket(url, handler) {
this._webSocketRoutes.unshift(new network.WebSocketRouteHandler(this._options.baseURL, url, handler));
await this._updateWebSocketInterceptionPatterns();
}
async _recordIntoHAR(har, page, options = {}) {
const { harId } = await this._channel.harStart({
page: page?._channel,
options: prepareRecordHarOptions({
path: har,
content: options.updateContent ?? "attach",
mode: options.updateMode ?? "minimal",
urlFilter: options.url
})
});
this._harRecorders.set(harId, { path: har, content: options.updateContent ?? "attach" });
}
async routeFromHAR(har, options = {}) {
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error("Route from har is not supported in thin clients");
if (options.update) {
await this._recordIntoHAR(har, null, options);
return;
}
const harRouter = await import_harRouter.HarRouter.create(localUtils, har, options.notFound || "abort", { urlMatch: options.url });
this._harRouters.push(harRouter);
await harRouter.addContextRoute(this);
}
_disposeHarRouters() {
this._harRouters.forEach((router) => router.dispose());
this._harRouters = [];
}
async unrouteAll(options) {
await this._unrouteInternal(this._routes, [], options?.behavior);
this._disposeHarRouters();
}
async unroute(url, handler) {
const removed = [];
const remaining = [];
for (const route of this._routes) {
if ((0, import_urlMatch.urlMatchesEqual)(route.url, url) && (!handler || route.handler === handler))
removed.push(route);
else
remaining.push(route);
}
await this._unrouteInternal(removed, remaining, "default");
}
async _unrouteInternal(removed, remaining, behavior) {
this._routes = remaining;
await this._updateInterceptionPatterns();
if (!behavior || behavior === "default")
return;
const promises = removed.map((routeHandler) => routeHandler.stop(behavior));
await Promise.all(promises);
}
async _updateInterceptionPatterns() {
const patterns = network.RouteHandler.prepareInterceptionPatterns(this._routes);
await this._channel.setNetworkInterceptionPatterns({ patterns });
}
async _updateWebSocketInterceptionPatterns() {
const patterns = network.WebSocketRouteHandler.prepareInterceptionPatterns(this._webSocketRoutes);
await this._channel.setWebSocketInterceptionPatterns({ patterns });
}
_effectiveCloseReason() {
return this._closeReason || this._browser?._closeReason;
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.BrowserContext.Close)
waiter.rejectOnEvent(this, import_events.Events.BrowserContext.Close, () => new import_errors.TargetClosedError(this._effectiveCloseReason()));
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
async storageState(options = {}) {
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, void 0, 2), "utf8");
}
return state;
}
backgroundPages() {
return [...this._backgroundPages];
}
serviceWorkers() {
return [...this._serviceWorkers];
}
async newCDPSession(page) {
if (!(page instanceof import_page.Page) && !(page instanceof import_frame.Frame))
throw new Error("page: expected Page or Frame");
const result = await this._channel.newCDPSession(page instanceof import_page.Page ? { page: page._channel } : { frame: page._channel });
return import_cdpSession.CDPSession.from(result.session);
}
_onClose() {
if (this._browser)
this._browser._contexts.delete(this);
this._browserType?._contexts?.delete(this);
this._disposeHarRouters();
this.tracing._resetStackCounter();
this.emit(import_events.Events.BrowserContext.Close, this);
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close(options = {}) {
if (this._closeWasCalled)
return;
this._closeReason = options.reason;
this._closeWasCalled = true;
await this._wrapApiCall(async () => {
await this.request.dispose(options);
}, true);
await this._wrapApiCall(async () => {
await this._browserType?._willCloseContext(this);
for (const [harId, harParams] of this._harRecorders) {
const har = await this._channel.harExport({ harId });
const artifact = import_artifact.Artifact.from(har.artifact);
const isCompressed = harParams.content === "attach" || harParams.path.endsWith(".zip");
const needCompressed = harParams.path.endsWith(".zip");
if (isCompressed && !needCompressed) {
const localUtils = this._connection.localUtils();
if (!localUtils)
throw new Error("Uncompressed har is not supported in thin clients");
await artifact.saveAs(harParams.path + ".tmp");
await localUtils.harUnzip({ zipFile: harParams.path + ".tmp", harFile: harParams.path });
} else {
await artifact.saveAs(harParams.path);
}
await artifact.delete();
}
}, true);
await this._channel.close(options);
await this._closedPromise;
}
async _enableRecorder(params) {
await this._channel.enableRecorder(params);
}
}
async function prepareStorageState(platform, options) {
if (typeof options.storageState !== "string")
return options.storageState;
try {
return JSON.parse(await platform.fs().promises.readFile(options.storageState, "utf8"));
} catch (e) {
(0, import_stackTrace.rewriteErrorMessage)(e, `Error reading storage state from ${options.storageState}:
` + e.message);
throw e;
}
}
function prepareRecordHarOptions(options) {
if (!options)
return;
return {
path: options.path,
content: options.content || (options.omitContent ? "omit" : void 0),
urlGlob: (0, import_rtti.isString)(options.urlFilter) ? options.urlFilter : void 0,
urlRegexSource: (0, import_rtti.isRegExp)(options.urlFilter) ? options.urlFilter.source : void 0,
urlRegexFlags: (0, import_rtti.isRegExp)(options.urlFilter) ? options.urlFilter.flags : void 0,
mode: options.mode
};
}
async function prepareBrowserContextParams(platform, options) {
if (options.videoSize && !options.videosPath)
throw new Error(`"videoSize" option requires "videosPath" to be specified`);
if (options.extraHTTPHeaders)
network.validateHeaders(options.extraHTTPHeaders);
const contextParams = {
...options,
viewport: options.viewport === null ? void 0 : options.viewport,
noDefaultViewport: options.viewport === null,
extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
storageState: await prepareStorageState(platform, options),
serviceWorkers: options.serviceWorkers,
recordHar: prepareRecordHarOptions(options.recordHar),
colorScheme: options.colorScheme === null ? "no-override" : options.colorScheme,
reducedMotion: options.reducedMotion === null ? "no-override" : options.reducedMotion,
forcedColors: options.forcedColors === null ? "no-override" : options.forcedColors,
contrast: options.contrast === null ? "no-override" : options.contrast,
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
clientCertificates: await toClientCertificatesProtocol(platform, options.clientCertificates)
};
if (!contextParams.recordVideo && options.videosPath) {
contextParams.recordVideo = {
dir: options.videosPath,
size: options.videoSize
};
}
if (contextParams.recordVideo && contextParams.recordVideo.dir)
contextParams.recordVideo.dir = platform.path().resolve(contextParams.recordVideo.dir);
return contextParams;
}
function toAcceptDownloadsProtocol(acceptDownloads) {
if (acceptDownloads === void 0)
return void 0;
if (acceptDownloads)
return "accept";
return "deny";
}
async function toClientCertificatesProtocol(platform, certs) {
if (!certs)
return void 0;
const bufferizeContent = async (value, path) => {
if (value)
return value;
if (path)
return await platform.fs().promises.readFile(path);
};
return await Promise.all(certs.map(async (cert) => ({
origin: cert.origin,
cert: await bufferizeContent(cert.cert, cert.certPath),
key: await bufferizeContent(cert.key, cert.keyPath),
pfx: await bufferizeContent(cert.pfx, cert.pfxPath),
passphrase: cert.passphrase
})));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BrowserContext,
prepareBrowserContextParams,
toClientCertificatesProtocol
});

View File

@@ -1,193 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var browserType_exports = {};
__export(browserType_exports, {
BrowserType: () => BrowserType
});
module.exports = __toCommonJS(browserType_exports);
var import_browser = require("./browser");
var import_browserContext = require("./browserContext");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_events = require("./events");
var import_assert = require("../utils/isomorphic/assert");
var import_headers = require("../utils/isomorphic/headers");
var import_time = require("../utils/isomorphic/time");
var import_timeoutRunner = require("../utils/isomorphic/timeoutRunner");
var import_webSocket = require("./webSocket");
class BrowserType extends import_channelOwner.ChannelOwner {
constructor() {
super(...arguments);
this._contexts = /* @__PURE__ */ new Set();
}
static from(browserType) {
return browserType._object;
}
executablePath() {
if (!this._initializer.executablePath)
throw new Error("Browser is not supported on current platform");
return this._initializer.executablePath;
}
name() {
return this._initializer.name;
}
async launch(options = {}) {
(0, import_assert.assert)(!options.userDataDir, "userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead");
(0, import_assert.assert)(!options.port, "Cannot specify a port without launching as a server.");
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
options = { ...this._playwright._defaultLaunchOptions, ...options };
const launchOptions = {
...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, import_clientHelper.envObjectToArray)(options.env) : void 0
};
return await this._wrapApiCall(async () => {
const browser = import_browser.Browser.from((await this._channel.launch(launchOptions)).browser);
this._didLaunchBrowser(browser, options, logger);
return browser;
});
}
async launchServer(options = {}) {
if (!this._serverLauncher)
throw new Error("Launching server is not supported");
options = { ...this._playwright._defaultLaunchOptions, ...options };
return await this._serverLauncher.launchServer(options);
}
async launchPersistentContext(userDataDir, options = {}) {
const logger = options.logger || this._playwright._defaultLaunchOptions?.logger;
(0, import_assert.assert)(!options.port, "Cannot specify a port without launching as a server.");
options = { ...this._playwright._defaultLaunchOptions, ...this._playwright._defaultContextOptions, ...options };
const contextParams = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const persistentParams = {
...contextParams,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, import_clientHelper.envObjectToArray)(options.env) : void 0,
channel: options.channel,
userDataDir: this._platform.path().isAbsolute(userDataDir) || !userDataDir ? userDataDir : this._platform.path().resolve(userDataDir)
};
return await this._wrapApiCall(async () => {
const result = await this._channel.launchPersistentContext(persistentParams);
const context = import_browserContext.BrowserContext.from(result.context);
await this._didCreateContext(context, contextParams, options, logger);
return context;
});
}
async connect(optionsOrWsEndpoint, options) {
if (typeof optionsOrWsEndpoint === "string")
return await this._connect({ ...options, wsEndpoint: optionsOrWsEndpoint });
(0, import_assert.assert)(optionsOrWsEndpoint.wsEndpoint, "options.wsEndpoint is required");
return await this._connect(optionsOrWsEndpoint);
}
async _connect(params) {
const logger = params.logger;
return await this._wrapApiCall(async () => {
const deadline = params.timeout ? (0, import_time.monotonicTime)() + params.timeout : 0;
const headers = { "x-playwright-browser": this.name(), ...params.headers };
const connectParams = {
wsEndpoint: params.wsEndpoint,
headers,
exposeNetwork: params.exposeNetwork ?? params._exposeNetwork,
slowMo: params.slowMo,
timeout: params.timeout
};
if (params.__testHookRedirectPortForwarding)
connectParams.socksProxyRedirectPortForTest = params.__testHookRedirectPortForwarding;
const connection = await (0, import_webSocket.connectOverWebSocket)(this._connection, connectParams);
let browser;
connection.on("close", () => {
for (const context of browser?.contexts() || []) {
for (const page of context.pages())
page._onClose();
context._onClose();
}
setTimeout(() => browser?._didClose(), 0);
});
const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
if (params.__testHookBeforeCreateBrowser)
await params.__testHookBeforeCreateBrowser();
const playwright = await connection.initializePlaywright();
if (!playwright._initializer.preLaunchedBrowser) {
connection.close();
throw new Error("Malformed endpoint. Did you use BrowserType.launchServer method?");
}
playwright._setSelectors(this._playwright.selectors);
browser = import_browser.Browser.from(playwright._initializer.preLaunchedBrowser);
this._didLaunchBrowser(browser, {}, logger);
browser._shouldCloseConnectionOnClose = true;
browser.on(import_events.Events.Browser.Disconnected, () => connection.close());
return browser;
}, deadline);
if (!result.timedOut) {
return result.result;
} else {
connection.close();
throw new Error(`Timeout ${params.timeout}ms exceeded`);
}
});
}
async connectOverCDP(endpointURLOrOptions, options) {
if (typeof endpointURLOrOptions === "string")
return await this._connectOverCDP(endpointURLOrOptions, options);
const endpointURL = "endpointURL" in endpointURLOrOptions ? endpointURLOrOptions.endpointURL : endpointURLOrOptions.wsEndpoint;
(0, import_assert.assert)(endpointURL, "Cannot connect over CDP without wsEndpoint.");
return await this.connectOverCDP(endpointURL, endpointURLOrOptions);
}
async _connectOverCDP(endpointURL, params = {}) {
if (this.name() !== "chromium")
throw new Error("Connecting over CDP is only supported in Chromium.");
const headers = params.headers ? (0, import_headers.headersObjectToArray)(params.headers) : void 0;
const result = await this._channel.connectOverCDP({
endpointURL,
headers,
slowMo: params.slowMo,
timeout: params.timeout
});
const browser = import_browser.Browser.from(result.browser);
this._didLaunchBrowser(browser, {}, params.logger);
if (result.defaultContext)
await this._didCreateContext(import_browserContext.BrowserContext.from(result.defaultContext), {}, {}, params.logger);
return browser;
}
_didLaunchBrowser(browser, browserOptions, logger) {
browser._browserType = this;
browser._options = browserOptions;
browser._logger = logger;
}
async _didCreateContext(context, contextOptions, browserOptions, logger) {
context._logger = logger;
context._browserType = this;
this._contexts.add(context);
context._setOptions(contextOptions, browserOptions);
if (this._playwright._defaultContextTimeout !== void 0)
context.setDefaultTimeout(this._playwright._defaultContextTimeout);
if (this._playwright._defaultContextNavigationTimeout !== void 0)
context.setDefaultNavigationTimeout(this._playwright._defaultContextNavigationTimeout);
await this._instrumentation.runAfterCreateBrowserContext(context);
}
async _willCloseContext(context) {
this._contexts.delete(context);
await this._instrumentation.runBeforeCloseBrowserContext(context);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BrowserType
});

View File

@@ -1,51 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var cdpSession_exports = {};
__export(cdpSession_exports, {
CDPSession: () => CDPSession
});
module.exports = __toCommonJS(cdpSession_exports);
var import_channelOwner = require("./channelOwner");
class CDPSession extends import_channelOwner.ChannelOwner {
static from(cdpSession) {
return cdpSession._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._channel.on("event", ({ method, params }) => {
this.emit(method, params);
});
this.on = super.on;
this.addListener = super.addListener;
this.off = super.removeListener;
this.removeListener = super.removeListener;
this.once = super.once;
}
async send(method, params) {
const result = await this._channel.send({ method, params });
return result.result;
}
async detach() {
return await this._channel.detach();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CDPSession
});

View File

@@ -1,205 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var channelOwner_exports = {};
__export(channelOwner_exports, {
ChannelOwner: () => ChannelOwner
});
module.exports = __toCommonJS(channelOwner_exports);
var import_eventEmitter = require("./eventEmitter");
var import_validator = require("../protocol/validator");
var import_clientStackTrace = require("./clientStackTrace");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
class ChannelOwner extends import_eventEmitter.EventEmitter {
constructor(parent, type, guid, initializer) {
const connection = parent instanceof ChannelOwner ? parent._connection : parent;
super(connection._platform);
this._objects = /* @__PURE__ */ new Map();
this._eventToSubscriptionMapping = /* @__PURE__ */ new Map();
this._isInternalType = false;
this._wasCollected = false;
this.setMaxListeners(0);
this._connection = connection;
this._type = type;
this._guid = guid;
this._parent = parent instanceof ChannelOwner ? parent : void 0;
this._instrumentation = this._connection._instrumentation;
this._connection._objects.set(guid, this);
if (this._parent) {
this._parent._objects.set(guid, this);
this._logger = this._parent._logger;
}
this._channel = this._createChannel(new import_eventEmitter.EventEmitter(connection._platform));
this._initializer = initializer;
}
markAsInternalType() {
this._isInternalType = true;
}
_setEventToSubscriptionMapping(mapping) {
this._eventToSubscriptionMapping = mapping;
}
_updateSubscription(event, enabled) {
const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
if (protocolEvent) {
this._wrapApiCall(async () => {
await this._channel.updateSubscription({ event: protocolEvent, enabled });
}, true).catch(() => {
});
}
}
on(event, listener) {
if (!this.listenerCount(event))
this._updateSubscription(event, true);
super.on(event, listener);
return this;
}
addListener(event, listener) {
if (!this.listenerCount(event))
this._updateSubscription(event, true);
super.addListener(event, listener);
return this;
}
prependListener(event, listener) {
if (!this.listenerCount(event))
this._updateSubscription(event, true);
super.prependListener(event, listener);
return this;
}
off(event, listener) {
super.off(event, listener);
if (!this.listenerCount(event))
this._updateSubscription(event, false);
return this;
}
removeListener(event, listener) {
super.removeListener(event, listener);
if (!this.listenerCount(event))
this._updateSubscription(event, false);
return this;
}
_adopt(child) {
child._parent._objects.delete(child._guid);
this._objects.set(child._guid, child);
child._parent = this;
}
_dispose(reason) {
if (this._parent)
this._parent._objects.delete(this._guid);
this._connection._objects.delete(this._guid);
this._wasCollected = reason === "gc";
for (const object of [...this._objects.values()])
object._dispose(reason);
this._objects.clear();
}
_debugScopeState() {
return {
_guid: this._guid,
objects: Array.from(this._objects.values()).map((o) => o._debugScopeState())
};
}
_validatorToWireContext() {
return {
tChannelImpl: tChannelImplToWire,
binary: this._connection.rawBuffers() ? "buffer" : "toBase64",
isUnderTest: () => this._platform.isUnderTest()
};
}
_createChannel(base) {
const channel = new Proxy(base, {
get: (obj, prop) => {
if (typeof prop === "string") {
const validator = (0, import_validator.maybeFindValidator)(this._type, prop, "Params");
if (validator) {
return async (params) => {
return await this._wrapApiCall(async (apiZone) => {
const validatedParams = validator(params, "", this._validatorToWireContext());
if (!apiZone.isInternal && !apiZone.reported) {
apiZone.params = params;
apiZone.reported = true;
this._instrumentation.onApiCallBegin(apiZone);
logApiCall(this._platform, this._logger, `=> ${apiZone.apiName} started`);
return await this._connection.sendMessageToServer(this, prop, validatedParams, apiZone.apiName, apiZone.frames, apiZone.stepId);
}
return await this._connection.sendMessageToServer(this, prop, validatedParams, void 0, [], void 0);
});
};
}
}
return obj[prop];
}
});
channel._object = this;
return channel;
}
async _wrapApiCall(func, isInternal) {
const logger = this._logger;
const existingApiZone = this._platform.zones.current().data();
if (existingApiZone)
return await func(existingApiZone);
if (isInternal === void 0)
isInternal = this._isInternalType;
const stackTrace = (0, import_clientStackTrace.captureLibraryStackTrace)(this._platform);
const apiZone = { apiName: stackTrace.apiName, frames: stackTrace.frames, isInternal, reported: false, userData: void 0, stepId: void 0 };
try {
const result = await this._platform.zones.current().push(apiZone).run(async () => await func(apiZone));
if (!isInternal) {
logApiCall(this._platform, logger, `<= ${apiZone.apiName} succeeded`);
this._instrumentation.onApiCallEnd(apiZone);
}
return result;
} catch (e) {
const innerError = (this._platform.showInternalStackFrames() || this._platform.isUnderTest()) && e.stack ? "\n<inner error>\n" + e.stack : "";
if (apiZone.apiName && !apiZone.apiName.includes("<anonymous>"))
e.message = apiZone.apiName + ": " + e.message;
const stackFrames = "\n" + (0, import_stackTrace.stringifyStackFrames)(stackTrace.frames).join("\n") + innerError;
if (stackFrames.trim())
e.stack = e.message + stackFrames;
else
e.stack = "";
if (!isInternal) {
apiZone.error = e;
logApiCall(this._platform, logger, `<= ${apiZone.apiName} failed`);
this._instrumentation.onApiCallEnd(apiZone);
}
throw e;
}
}
_toImpl() {
return this._connection.toImpl?.(this);
}
toJSON() {
return {
_type: this._type,
_guid: this._guid
};
}
}
function logApiCall(platform, logger, message) {
if (logger && logger.isEnabled("api", "info"))
logger.log("api", "info", message, [], { color: "cyan" });
platform.log("api", message);
}
function tChannelImplToWire(names, arg, path, context) {
if (arg._object instanceof ChannelOwner && (names === "*" || names.includes(arg._object._type)))
return { guid: arg._object._guid };
throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ChannelOwner
});

View File

@@ -1,64 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var clientHelper_exports = {};
__export(clientHelper_exports, {
addSourceUrlToScript: () => addSourceUrlToScript,
envObjectToArray: () => envObjectToArray,
evaluationScript: () => evaluationScript
});
module.exports = __toCommonJS(clientHelper_exports);
var import_rtti = require("../utils/isomorphic/rtti");
function envObjectToArray(env) {
const result = [];
for (const name in env) {
if (!Object.is(env[name], void 0))
result.push({ name, value: String(env[name]) });
}
return result;
}
async function evaluationScript(platform, fun, arg, addSourceUrl = true) {
if (typeof fun === "function") {
const source = fun.toString();
const argString = Object.is(arg, void 0) ? "undefined" : JSON.stringify(arg);
return `(${source})(${argString})`;
}
if (arg !== void 0)
throw new Error("Cannot evaluate a string with arguments");
if ((0, import_rtti.isString)(fun))
return fun;
if (fun.content !== void 0)
return fun.content;
if (fun.path !== void 0) {
let source = await platform.fs().promises.readFile(fun.path, "utf8");
if (addSourceUrl)
source = addSourceUrlToScript(source, fun.path);
return source;
}
throw new Error("Either path or content property must be present");
}
function addSourceUrlToScript(source, path) {
return `${source}
//# sourceURL=${path.replace(/\n/g, "")}`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
addSourceUrlToScript,
envObjectToArray,
evaluationScript
});

View File

@@ -1,55 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var clientInstrumentation_exports = {};
__export(clientInstrumentation_exports, {
createInstrumentation: () => createInstrumentation
});
module.exports = __toCommonJS(clientInstrumentation_exports);
function createInstrumentation() {
const listeners = [];
return new Proxy({}, {
get: (obj, prop) => {
if (typeof prop !== "string")
return obj[prop];
if (prop === "addListener")
return (listener) => listeners.push(listener);
if (prop === "removeListener")
return (listener) => listeners.splice(listeners.indexOf(listener), 1);
if (prop === "removeAllListeners")
return () => listeners.splice(0, listeners.length);
if (prop.startsWith("run")) {
return async (...params) => {
for (const listener of listeners)
await listener[prop]?.(...params);
};
}
if (prop.startsWith("on")) {
return (...params) => {
for (const listener of listeners)
listener[prop]?.(...params);
};
}
return obj[prop];
}
});
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createInstrumentation
});

View File

@@ -1,68 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var clock_exports = {};
__export(clock_exports, {
Clock: () => Clock
});
module.exports = __toCommonJS(clock_exports);
class Clock {
constructor(browserContext) {
this._browserContext = browserContext;
}
async install(options = {}) {
await this._browserContext._channel.clockInstall(options.time !== void 0 ? parseTime(options.time) : {});
}
async fastForward(ticks) {
await this._browserContext._channel.clockFastForward(parseTicks(ticks));
}
async pauseAt(time) {
await this._browserContext._channel.clockPauseAt(parseTime(time));
}
async resume() {
await this._browserContext._channel.clockResume({});
}
async runFor(ticks) {
await this._browserContext._channel.clockRunFor(parseTicks(ticks));
}
async setFixedTime(time) {
await this._browserContext._channel.clockSetFixedTime(parseTime(time));
}
async setSystemTime(time) {
await this._browserContext._channel.clockSetSystemTime(parseTime(time));
}
}
function parseTime(time) {
if (typeof time === "number")
return { timeNumber: time };
if (typeof time === "string")
return { timeString: time };
if (!isFinite(time.getTime()))
throw new Error(`Invalid date: ${time}`);
return { timeNumber: time.getTime() };
}
function parseTicks(ticks) {
return {
ticksNumber: typeof ticks === "number" ? ticks : void 0,
ticksString: typeof ticks === "string" ? ticks : void 0
};
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Clock
});

View File

@@ -1,318 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var connection_exports = {};
__export(connection_exports, {
Connection: () => Connection
});
module.exports = __toCommonJS(connection_exports);
var import_eventEmitter = require("./eventEmitter");
var import_android = require("./android");
var import_artifact = require("./artifact");
var import_browser = require("./browser");
var import_browserContext = require("./browserContext");
var import_browserType = require("./browserType");
var import_cdpSession = require("./cdpSession");
var import_channelOwner = require("./channelOwner");
var import_clientInstrumentation = require("./clientInstrumentation");
var import_dialog = require("./dialog");
var import_electron = require("./electron");
var import_elementHandle = require("./elementHandle");
var import_errors = require("./errors");
var import_fetch = require("./fetch");
var import_frame = require("./frame");
var import_jsHandle = require("./jsHandle");
var import_jsonPipe = require("./jsonPipe");
var import_localUtils = require("./localUtils");
var import_network = require("./network");
var import_page = require("./page");
var import_playwright = require("./playwright");
var import_selectors = require("./selectors");
var import_stream = require("./stream");
var import_tracing = require("./tracing");
var import_worker = require("./worker");
var import_writableStream = require("./writableStream");
var import_validator = require("../protocol/validator");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
class Root extends import_channelOwner.ChannelOwner {
constructor(connection) {
super(connection, "Root", "", {});
}
async initialize() {
return import_playwright.Playwright.from((await this._channel.initialize({
sdkLanguage: "javascript"
})).playwright);
}
}
class DummyChannelOwner extends import_channelOwner.ChannelOwner {
}
class Connection extends import_eventEmitter.EventEmitter {
constructor(platform, localUtils, instrumentation, headers = []) {
super(platform);
this._objects = /* @__PURE__ */ new Map();
this.onmessage = (message) => {
};
this._lastId = 0;
this._callbacks = /* @__PURE__ */ new Map();
this._isRemote = false;
this._rawBuffers = false;
this._tracingCount = 0;
this._instrumentation = instrumentation || (0, import_clientInstrumentation.createInstrumentation)();
this._localUtils = localUtils;
this._rootObject = new Root(this);
this.headers = headers;
}
markAsRemote() {
this._isRemote = true;
}
isRemote() {
return this._isRemote;
}
useRawBuffers() {
this._rawBuffers = true;
}
rawBuffers() {
return this._rawBuffers;
}
localUtils() {
return this._localUtils;
}
async initializePlaywright() {
return await this._rootObject.initialize();
}
getObjectWithKnownName(guid) {
return this._objects.get(guid);
}
setIsTracing(isTracing) {
if (isTracing)
this._tracingCount++;
else
this._tracingCount--;
}
async sendMessageToServer(object, method, params, apiName, frames, stepId) {
if (this._closedError)
throw this._closedError;
if (object._wasCollected)
throw new Error("The object has been collected to prevent unbounded heap growth.");
const guid = object._guid;
const type = object._type;
const id = ++this._lastId;
const message = { id, guid, method, params };
if (this._platform.isLogEnabled("channel")) {
this._platform.log("channel", "SEND> " + JSON.stringify(message));
}
const location = frames[0] ? { file: frames[0].file, line: frames[0].line, column: frames[0].column } : void 0;
const metadata = { apiName, location, internal: !apiName, stepId };
if (this._tracingCount && frames && type !== "LocalUtils")
this._localUtils?.addStackToTracingNoReply({ callData: { stack: frames, id } }).catch(() => {
});
this._platform.zones.empty.run(() => this.onmessage({ ...message, metadata }));
return await new Promise((resolve, reject) => this._callbacks.set(id, { resolve, reject, apiName, type, method }));
}
_validatorFromWireContext() {
return {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this._rawBuffers ? "buffer" : "fromBase64",
isUnderTest: () => this._platform.isUnderTest()
};
}
dispatch(message) {
if (this._closedError)
return;
const { id, guid, method, params, result, error, log } = message;
if (id) {
if (this._platform.isLogEnabled("channel"))
this._platform.log("channel", "<RECV " + JSON.stringify(message));
const callback = this._callbacks.get(id);
if (!callback)
throw new Error(`Cannot find command to respond: ${id}`);
this._callbacks.delete(id);
if (error && !result) {
const parsedError = (0, import_errors.parseError)(error);
(0, import_stackTrace.rewriteErrorMessage)(parsedError, parsedError.message + formatCallLog(this._platform, log));
callback.reject(parsedError);
} else {
const validator2 = (0, import_validator.findValidator)(callback.type, callback.method, "Result");
callback.resolve(validator2(result, "", this._validatorFromWireContext()));
}
return;
}
if (this._platform.isLogEnabled("channel"))
this._platform.log("channel", "<EVENT " + JSON.stringify(message));
if (method === "__create__") {
this._createRemoteObject(guid, params.type, params.guid, params.initializer);
return;
}
const object = this._objects.get(guid);
if (!object)
throw new Error(`Cannot find object to "${method}": ${guid}`);
if (method === "__adopt__") {
const child = this._objects.get(params.guid);
if (!child)
throw new Error(`Unknown new child: ${params.guid}`);
object._adopt(child);
return;
}
if (method === "__dispose__") {
object._dispose(params.reason);
return;
}
const validator = (0, import_validator.findValidator)(object._type, method, "Event");
object._channel.emit(method, validator(params, "", this._validatorFromWireContext()));
}
close(cause) {
if (this._closedError)
return;
this._closedError = new import_errors.TargetClosedError(cause);
for (const callback of this._callbacks.values())
callback.reject(this._closedError);
this._callbacks.clear();
this.emit("close");
}
_tChannelImplFromWire(names, arg, path, context) {
if (arg && typeof arg === "object" && typeof arg.guid === "string") {
const object = this._objects.get(arg.guid);
if (!object)
throw new Error(`Object with guid ${arg.guid} was not bound in the connection`);
if (names !== "*" && !names.includes(object._type))
throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
return object._channel;
}
throw new import_validator.ValidationError(`${path}: expected channel ${names.toString()}`);
}
_createRemoteObject(parentGuid, type, guid, initializer) {
const parent = this._objects.get(parentGuid);
if (!parent)
throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
let result;
const validator = (0, import_validator.findValidator)(type, "", "Initializer");
initializer = validator(initializer, "", this._validatorFromWireContext());
switch (type) {
case "Android":
result = new import_android.Android(parent, type, guid, initializer);
break;
case "AndroidSocket":
result = new import_android.AndroidSocket(parent, type, guid, initializer);
break;
case "AndroidDevice":
result = new import_android.AndroidDevice(parent, type, guid, initializer);
break;
case "APIRequestContext":
result = new import_fetch.APIRequestContext(parent, type, guid, initializer);
break;
case "Artifact":
result = new import_artifact.Artifact(parent, type, guid, initializer);
break;
case "BindingCall":
result = new import_page.BindingCall(parent, type, guid, initializer);
break;
case "Browser":
result = new import_browser.Browser(parent, type, guid, initializer);
break;
case "BrowserContext":
result = new import_browserContext.BrowserContext(parent, type, guid, initializer);
break;
case "BrowserType":
result = new import_browserType.BrowserType(parent, type, guid, initializer);
break;
case "CDPSession":
result = new import_cdpSession.CDPSession(parent, type, guid, initializer);
break;
case "Dialog":
result = new import_dialog.Dialog(parent, type, guid, initializer);
break;
case "Electron":
result = new import_electron.Electron(parent, type, guid, initializer);
break;
case "ElectronApplication":
result = new import_electron.ElectronApplication(parent, type, guid, initializer);
break;
case "ElementHandle":
result = new import_elementHandle.ElementHandle(parent, type, guid, initializer);
break;
case "Frame":
result = new import_frame.Frame(parent, type, guid, initializer);
break;
case "JSHandle":
result = new import_jsHandle.JSHandle(parent, type, guid, initializer);
break;
case "JsonPipe":
result = new import_jsonPipe.JsonPipe(parent, type, guid, initializer);
break;
case "LocalUtils":
result = new import_localUtils.LocalUtils(parent, type, guid, initializer);
if (!this._localUtils)
this._localUtils = result;
break;
case "Page":
result = new import_page.Page(parent, type, guid, initializer);
break;
case "Playwright":
result = new import_playwright.Playwright(parent, type, guid, initializer);
break;
case "Request":
result = new import_network.Request(parent, type, guid, initializer);
break;
case "Response":
result = new import_network.Response(parent, type, guid, initializer);
break;
case "Route":
result = new import_network.Route(parent, type, guid, initializer);
break;
case "Stream":
result = new import_stream.Stream(parent, type, guid, initializer);
break;
case "Selectors":
result = new import_selectors.SelectorsOwner(parent, type, guid, initializer);
break;
case "SocksSupport":
result = new DummyChannelOwner(parent, type, guid, initializer);
break;
case "Tracing":
result = new import_tracing.Tracing(parent, type, guid, initializer);
break;
case "WebSocket":
result = new import_network.WebSocket(parent, type, guid, initializer);
break;
case "WebSocketRoute":
result = new import_network.WebSocketRoute(parent, type, guid, initializer);
break;
case "Worker":
result = new import_worker.Worker(parent, type, guid, initializer);
break;
case "WritableStream":
result = new import_writableStream.WritableStream(parent, type, guid, initializer);
break;
default:
throw new Error("Missing type " + type);
}
return result;
}
}
function formatCallLog(platform, log) {
if (!log || !log.some((l) => !!l))
return "";
return `
Call log:
${platform.colors.dim(log.join("\n"))}
`;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Connection
});

View File

@@ -1,55 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var consoleMessage_exports = {};
__export(consoleMessage_exports, {
ConsoleMessage: () => ConsoleMessage
});
module.exports = __toCommonJS(consoleMessage_exports);
var import_jsHandle = require("./jsHandle");
var import_page = require("./page");
class ConsoleMessage {
constructor(platform, event) {
this._page = "page" in event && event.page ? import_page.Page.from(event.page) : null;
this._event = event;
if (platform.inspectCustom)
this[platform.inspectCustom] = () => this._inspect();
}
page() {
return this._page;
}
type() {
return this._event.type;
}
text() {
return this._event.text;
}
args() {
return this._event.args.map(import_jsHandle.JSHandle.from);
}
location() {
return this._event.location;
}
_inspect() {
return this.text();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ConsoleMessage
});

View File

@@ -1,44 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var coverage_exports = {};
__export(coverage_exports, {
Coverage: () => Coverage
});
module.exports = __toCommonJS(coverage_exports);
class Coverage {
constructor(channel) {
this._channel = channel;
}
async startJSCoverage(options = {}) {
await this._channel.startJSCoverage(options);
}
async stopJSCoverage() {
return (await this._channel.stopJSCoverage()).entries;
}
async startCSSCoverage(options = {}) {
await this._channel.startCSSCoverage(options);
}
async stopCSSCoverage() {
return (await this._channel.stopCSSCoverage()).entries;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Coverage
});

View File

@@ -1,56 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var dialog_exports = {};
__export(dialog_exports, {
Dialog: () => Dialog
});
module.exports = __toCommonJS(dialog_exports);
var import_channelOwner = require("./channelOwner");
var import_page = require("./page");
class Dialog extends import_channelOwner.ChannelOwner {
static from(dialog) {
return dialog._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._page = import_page.Page.fromNullable(initializer.page);
}
page() {
return this._page;
}
type() {
return this._initializer.type;
}
message() {
return this._initializer.message;
}
defaultValue() {
return this._initializer.defaultValue;
}
async accept(promptText) {
await this._channel.accept({ promptText });
}
async dismiss() {
await this._channel.dismiss();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Dialog
});

View File

@@ -1,62 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var download_exports = {};
__export(download_exports, {
Download: () => Download
});
module.exports = __toCommonJS(download_exports);
class Download {
constructor(page, url, suggestedFilename, artifact) {
this._page = page;
this._url = url;
this._suggestedFilename = suggestedFilename;
this._artifact = artifact;
}
page() {
return this._page;
}
url() {
return this._url;
}
suggestedFilename() {
return this._suggestedFilename;
}
async path() {
return await this._artifact.pathAfterFinished();
}
async saveAs(path) {
return await this._artifact.saveAs(path);
}
async failure() {
return await this._artifact.failure();
}
async createReadStream() {
return await this._artifact.createReadStream();
}
async cancel() {
return await this._artifact.cancel();
}
async delete() {
return await this._artifact.delete();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Download
});

View File

@@ -1,133 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var electron_exports = {};
__export(electron_exports, {
Electron: () => Electron,
ElectronApplication: () => ElectronApplication
});
module.exports = __toCommonJS(electron_exports);
var import_browserContext = require("./browserContext");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_consoleMessage = require("./consoleMessage");
var import_errors = require("./errors");
var import_events = require("./events");
var import_jsHandle = require("./jsHandle");
var import_waiter = require("./waiter");
var import_timeoutSettings = require("./timeoutSettings");
class Electron extends import_channelOwner.ChannelOwner {
static from(electron) {
return electron._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
}
async launch(options = {}) {
const params = {
...await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options),
env: (0, import_clientHelper.envObjectToArray)(options.env ? options.env : this._platform.env),
tracesDir: options.tracesDir
};
const app = ElectronApplication.from((await this._channel.launch(params)).electronApplication);
app._context._setOptions(params, options);
return app;
}
}
class ElectronApplication extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._windows = /* @__PURE__ */ new Set();
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
this._context = import_browserContext.BrowserContext.from(initializer.context);
for (const page of this._context._pages)
this._onPage(page);
this._context.on(import_events.Events.BrowserContext.Page, (page) => this._onPage(page));
this._channel.on("close", () => {
this.emit(import_events.Events.ElectronApplication.Close);
});
this._channel.on("console", (event) => this.emit(import_events.Events.ElectronApplication.Console, new import_consoleMessage.ConsoleMessage(this._platform, event)));
this._setEventToSubscriptionMapping(/* @__PURE__ */ new Map([
[import_events.Events.ElectronApplication.Console, "console"]
]));
}
static from(electronApplication) {
return electronApplication._object;
}
process() {
return this._toImpl().process();
}
_onPage(page) {
this._windows.add(page);
this.emit(import_events.Events.ElectronApplication.Window, page);
page.once(import_events.Events.Page.Close, () => this._windows.delete(page));
}
windows() {
return [...this._windows];
}
async firstWindow(options) {
if (this._windows.size)
return this._windows.values().next().value;
return await this.waitForEvent("window", options);
}
context() {
return this._context;
}
async [Symbol.asyncDispose]() {
await this.close();
}
async close() {
try {
await this._context.close();
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeout = this._timeoutSettings.timeout(typeof optionsOrPredicate === "function" ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === "function" ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = import_waiter.Waiter.createForEvent(this, event);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded while waiting for event "${event}"`);
if (event !== import_events.Events.ElectronApplication.Close)
waiter.rejectOnEvent(this, import_events.Events.ElectronApplication.Close, () => new import_errors.TargetClosedError());
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
async browserWindow(page) {
const result = await this._channel.browserWindow({ page: page._channel });
return import_jsHandle.JSHandle.from(result.handle);
}
async evaluate(pageFunction, arg) {
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async evaluateHandle(pageFunction, arg) {
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return import_jsHandle.JSHandle.from(result.handle);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Electron,
ElectronApplication
});

View File

@@ -1,284 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var elementHandle_exports = {};
__export(elementHandle_exports, {
ElementHandle: () => ElementHandle,
convertInputFiles: () => convertInputFiles,
convertSelectOptionValues: () => convertSelectOptionValues,
determineScreenshotType: () => determineScreenshotType
});
module.exports = __toCommonJS(elementHandle_exports);
var import_frame = require("./frame");
var import_jsHandle = require("./jsHandle");
var import_assert = require("../utils/isomorphic/assert");
var import_fileUtils = require("./fileUtils");
var import_rtti = require("../utils/isomorphic/rtti");
var import_writableStream = require("./writableStream");
var import_mimeType = require("../utils/isomorphic/mimeType");
class ElementHandle extends import_jsHandle.JSHandle {
static from(handle) {
return handle._object;
}
static fromNullable(handle) {
return handle ? ElementHandle.from(handle) : null;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._elementChannel = this._channel;
}
asElement() {
return this;
}
async ownerFrame() {
return import_frame.Frame.fromNullable((await this._elementChannel.ownerFrame()).frame);
}
async contentFrame() {
return import_frame.Frame.fromNullable((await this._elementChannel.contentFrame()).frame);
}
async _generateLocatorString() {
const value = (await this._elementChannel.generateLocatorString()).value;
return value === void 0 ? null : value;
}
async getAttribute(name) {
const value = (await this._elementChannel.getAttribute({ name })).value;
return value === void 0 ? null : value;
}
async inputValue() {
return (await this._elementChannel.inputValue()).value;
}
async textContent() {
const value = (await this._elementChannel.textContent()).value;
return value === void 0 ? null : value;
}
async innerText() {
return (await this._elementChannel.innerText()).value;
}
async innerHTML() {
return (await this._elementChannel.innerHTML()).value;
}
async isChecked() {
return (await this._elementChannel.isChecked()).value;
}
async isDisabled() {
return (await this._elementChannel.isDisabled()).value;
}
async isEditable() {
return (await this._elementChannel.isEditable()).value;
}
async isEnabled() {
return (await this._elementChannel.isEnabled()).value;
}
async isHidden() {
return (await this._elementChannel.isHidden()).value;
}
async isVisible() {
return (await this._elementChannel.isVisible()).value;
}
async dispatchEvent(type, eventInit = {}) {
await this._elementChannel.dispatchEvent({ type, eventInit: (0, import_jsHandle.serializeArgument)(eventInit) });
}
async scrollIntoViewIfNeeded(options = {}) {
await this._elementChannel.scrollIntoViewIfNeeded(options);
}
async hover(options = {}) {
await this._elementChannel.hover(options);
}
async click(options = {}) {
return await this._elementChannel.click(options);
}
async dblclick(options = {}) {
return await this._elementChannel.dblclick(options);
}
async tap(options = {}) {
return await this._elementChannel.tap(options);
}
async selectOption(values, options = {}) {
const result = await this._elementChannel.selectOption({ ...convertSelectOptionValues(values), ...options });
return result.values;
}
async fill(value, options = {}) {
return await this._elementChannel.fill({ value, ...options });
}
async selectText(options = {}) {
await this._elementChannel.selectText(options);
}
async setInputFiles(files, options = {}) {
const frame = await this.ownerFrame();
if (!frame)
throw new Error("Cannot set input files to detached element");
const converted = await convertInputFiles(this._platform, files, frame.page().context());
await this._elementChannel.setInputFiles({ ...converted, ...options });
}
async focus() {
await this._elementChannel.focus();
}
async type(text, options = {}) {
await this._elementChannel.type({ text, ...options });
}
async press(key, options = {}) {
await this._elementChannel.press({ key, ...options });
}
async check(options = {}) {
return await this._elementChannel.check(options);
}
async uncheck(options = {}) {
return await this._elementChannel.uncheck(options);
}
async setChecked(checked, options) {
if (checked)
await this.check(options);
else
await this.uncheck(options);
}
async boundingBox() {
const value = (await this._elementChannel.boundingBox()).value;
return value === void 0 ? null : value;
}
async screenshot(options = {}) {
const mask = options.mask;
const copy = { ...options, mask: void 0 };
if (!copy.type)
copy.type = determineScreenshotType(options);
if (mask) {
copy.mask = mask.map((locator) => ({
frame: locator._frame._channel,
selector: locator._selector
}));
}
const result = await this._elementChannel.screenshot(copy);
if (options.path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
await this._platform.fs().promises.writeFile(options.path, result.binary);
}
return result.binary;
}
async $(selector) {
return ElementHandle.fromNullable((await this._elementChannel.querySelector({ selector })).element);
}
async $$(selector) {
const result = await this._elementChannel.querySelectorAll({ selector });
return result.elements.map((h) => ElementHandle.from(h));
}
async $eval(selector, pageFunction, arg) {
const result = await this._elementChannel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async $$eval(selector, pageFunction, arg) {
const result = await this._elementChannel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async waitForElementState(state, options = {}) {
return await this._elementChannel.waitForElementState({ state, ...options });
}
async waitForSelector(selector, options = {}) {
const result = await this._elementChannel.waitForSelector({ selector, ...options });
return ElementHandle.fromNullable(result.element);
}
}
function convertSelectOptionValues(values) {
if (values === null)
return {};
if (!Array.isArray(values))
values = [values];
if (!values.length)
return {};
for (let i = 0; i < values.length; i++)
(0, import_assert.assert)(values[i] !== null, `options[${i}]: expected object, got null`);
if (values[0] instanceof ElementHandle)
return { elements: values.map((v) => v._elementChannel) };
if ((0, import_rtti.isString)(values[0]))
return { options: values.map((valueOrLabel) => ({ valueOrLabel })) };
return { options: values };
}
function filePayloadExceedsSizeLimit(payloads) {
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= import_fileUtils.fileUploadSizeLimit;
}
async function resolvePathsAndDirectoryForInputFiles(platform, items) {
let localPaths;
let localDirectory;
for (const item of items) {
const stat = await platform.fs().promises.stat(item);
if (stat.isDirectory()) {
if (localDirectory)
throw new Error("Multiple directories are not supported");
localDirectory = platform.path().resolve(item);
} else {
localPaths ??= [];
localPaths.push(platform.path().resolve(item));
}
}
if (localPaths?.length && localDirectory)
throw new Error("File paths must be all files or a single directory");
return [localPaths, localDirectory];
}
async function convertInputFiles(platform, files, context) {
const items = Array.isArray(files) ? files.slice() : [files];
if (items.some((item) => typeof item === "string")) {
if (!items.every((item) => typeof item === "string"))
throw new Error("File paths cannot be mixed with buffers");
const [localPaths, localDirectory] = await resolvePathsAndDirectoryForInputFiles(platform, items);
if (context._connection.isRemote()) {
const files2 = localDirectory ? (await platform.fs().promises.readdir(localDirectory, { withFileTypes: true, recursive: true })).filter((f) => f.isFile()).map((f) => platform.path().join(f.path, f.name)) : localPaths;
const { writableStreams, rootDir } = await context._wrapApiCall(async () => context._channel.createTempFiles({
rootDirName: localDirectory ? platform.path().basename(localDirectory) : void 0,
items: await Promise.all(files2.map(async (file) => {
const lastModifiedMs = (await platform.fs().promises.stat(file)).mtimeMs;
return {
name: localDirectory ? platform.path().relative(localDirectory, file) : platform.path().basename(file),
lastModifiedMs
};
}))
}), true);
for (let i = 0; i < files2.length; i++) {
const writable = import_writableStream.WritableStream.from(writableStreams[i]);
await platform.streamFile(files2[i], writable.stream());
}
return {
directoryStream: rootDir,
streams: localDirectory ? void 0 : writableStreams
};
}
return {
localPaths,
localDirectory
};
}
const payloads = items;
if (filePayloadExceedsSizeLimit(payloads))
throw new Error("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
return { payloads };
}
function determineScreenshotType(options) {
if (options.path) {
const mimeType = (0, import_mimeType.getMimeTypeForPath)(options.path);
if (mimeType === "image/png")
return "png";
else if (mimeType === "image/jpeg")
return "jpeg";
throw new Error(`path: unsupported mime type "${mimeType}"`);
}
return options.type;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ElementHandle,
convertInputFiles,
convertSelectOptionValues,
determineScreenshotType
});

View File

@@ -1,77 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var errors_exports = {};
__export(errors_exports, {
TargetClosedError: () => TargetClosedError,
TimeoutError: () => TimeoutError,
isTargetClosedError: () => isTargetClosedError,
parseError: () => parseError,
serializeError: () => serializeError
});
module.exports = __toCommonJS(errors_exports);
var import_serializers = require("../protocol/serializers");
var import_rtti = require("../utils/isomorphic/rtti");
class TimeoutError extends Error {
constructor(message) {
super(message);
this.name = "TimeoutError";
}
}
class TargetClosedError extends Error {
constructor(cause) {
super(cause || "Target page, context or browser has been closed");
}
}
function isTargetClosedError(error) {
return error instanceof TargetClosedError;
}
function serializeError(e) {
if ((0, import_rtti.isError)(e))
return { error: { message: e.message, stack: e.stack, name: e.name } };
return { value: (0, import_serializers.serializeValue)(e, (value) => ({ fallThrough: value })) };
}
function parseError(error) {
if (!error.error) {
if (error.value === void 0)
throw new Error("Serialized error must have either an error or a value");
return (0, import_serializers.parseSerializedValue)(error.value, void 0);
}
if (error.error.name === "TimeoutError") {
const e2 = new TimeoutError(error.error.message);
e2.stack = error.error.stack || "";
return e2;
}
if (error.error.name === "TargetClosedError") {
const e2 = new TargetClosedError(error.error.message);
e2.stack = error.error.stack || "";
return e2;
}
const e = new Error(error.error.message);
e.stack = error.error.stack || "";
e.name = error.error.name;
return e;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
TargetClosedError,
TimeoutError,
isTargetClosedError,
parseError,
serializeError
});

View File

@@ -1,314 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var eventEmitter_exports = {};
__export(eventEmitter_exports, {
EventEmitter: () => EventEmitter
});
module.exports = __toCommonJS(eventEmitter_exports);
class EventEmitter {
constructor(platform) {
this._events = void 0;
this._eventsCount = 0;
this._maxListeners = void 0;
this._pendingHandlers = /* @__PURE__ */ new Map();
this._platform = platform;
if (this._events === void 0 || this._events === Object.getPrototypeOf(this)._events) {
this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || void 0;
this.on = this.addListener;
this.off = this.removeListener;
}
setMaxListeners(n) {
if (typeof n !== "number" || n < 0 || Number.isNaN(n))
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + ".");
this._maxListeners = n;
return this;
}
getMaxListeners() {
return this._maxListeners === void 0 ? this._platform.defaultMaxListeners() : this._maxListeners;
}
emit(type, ...args) {
const events = this._events;
if (events === void 0)
return false;
const handler = events?.[type];
if (handler === void 0)
return false;
if (typeof handler === "function") {
this._callHandler(type, handler, args);
} else {
const len = handler.length;
const listeners = handler.slice();
for (let i = 0; i < len; ++i)
this._callHandler(type, listeners[i], args);
}
return true;
}
_callHandler(type, handler, args) {
const promise = Reflect.apply(handler, this, args);
if (!(promise instanceof Promise))
return;
let set = this._pendingHandlers.get(type);
if (!set) {
set = /* @__PURE__ */ new Set();
this._pendingHandlers.set(type, set);
}
set.add(promise);
promise.catch((e) => {
if (this._rejectionHandler)
this._rejectionHandler(e);
else
throw e;
}).finally(() => set.delete(promise));
}
addListener(type, listener) {
return this._addListener(type, listener, false);
}
on(type, listener) {
return this._addListener(type, listener, false);
}
_addListener(type, listener, prepend) {
checkListener(listener);
let events = this._events;
let existing;
if (events === void 0) {
events = this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
} else {
if (events.newListener !== void 0) {
this.emit("newListener", type, unwrapListener(listener));
events = this._events;
}
existing = events[type];
}
if (existing === void 0) {
existing = events[type] = listener;
++this._eventsCount;
} else {
if (typeof existing === "function") {
existing = events[type] = prepend ? [listener, existing] : [existing, listener];
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
const m = this.getMaxListeners();
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
const w = new Error("Possible EventEmitter memory leak detected. " + existing.length + " " + String(type) + " listeners added. Use emitter.setMaxListeners() to increase limit");
w.name = "MaxListenersExceededWarning";
w.emitter = this;
w.type = type;
w.count = existing.length;
if (!this._platform.isUnderTest()) {
console.warn(w);
}
}
}
return this;
}
prependListener(type, listener) {
return this._addListener(type, listener, true);
}
once(type, listener) {
checkListener(listener);
this.on(type, new OnceWrapper(this, type, listener).wrapperFunction);
return this;
}
prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, new OnceWrapper(this, type, listener).wrapperFunction);
return this;
}
removeListener(type, listener) {
checkListener(listener);
const events = this._events;
if (events === void 0)
return this;
const list = events[type];
if (list === void 0)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0) {
this._events = /* @__PURE__ */ Object.create(null);
} else {
delete events[type];
if (events.removeListener)
this.emit("removeListener", type, list.listener ?? listener);
}
} else if (typeof list !== "function") {
let position = -1;
let originalListener;
for (let i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || wrappedListener(list[i]) === listener) {
originalListener = wrappedListener(list[i]);
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else
list.splice(position, 1);
if (list.length === 1)
events[type] = list[0];
if (events.removeListener !== void 0)
this.emit("removeListener", type, originalListener || listener);
}
return this;
}
off(type, listener) {
return this.removeListener(type, listener);
}
removeAllListeners(type, options) {
this._removeAllListeners(type);
if (!options)
return this;
if (options.behavior === "wait") {
const errors = [];
this._rejectionHandler = (error) => errors.push(error);
return this._waitFor(type).then(() => {
if (errors.length)
throw errors[0];
});
}
if (options.behavior === "ignoreErrors")
this._rejectionHandler = () => {
};
return Promise.resolve();
}
_removeAllListeners(type) {
const events = this._events;
if (!events)
return;
if (!events.removeListener) {
if (type === void 0) {
this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== void 0) {
if (--this._eventsCount === 0)
this._events = /* @__PURE__ */ Object.create(null);
else
delete events[type];
}
return;
}
if (type === void 0) {
const keys = Object.keys(events);
let key;
for (let i = 0; i < keys.length; ++i) {
key = keys[i];
if (key === "removeListener")
continue;
this._removeAllListeners(key);
}
this._removeAllListeners("removeListener");
this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
return;
}
const listeners = events[type];
if (typeof listeners === "function") {
this.removeListener(type, listeners);
} else if (listeners !== void 0) {
for (let i = listeners.length - 1; i >= 0; i--)
this.removeListener(type, listeners[i]);
}
}
listeners(type) {
return this._listeners(this, type, true);
}
rawListeners(type) {
return this._listeners(this, type, false);
}
listenerCount(type) {
const events = this._events;
if (events !== void 0) {
const listener = events[type];
if (typeof listener === "function")
return 1;
if (listener !== void 0)
return listener.length;
}
return 0;
}
eventNames() {
return this._eventsCount > 0 && this._events ? Reflect.ownKeys(this._events) : [];
}
async _waitFor(type) {
let promises = [];
if (type) {
promises = [...this._pendingHandlers.get(type) || []];
} else {
promises = [];
for (const [, pending] of this._pendingHandlers)
promises.push(...pending);
}
await Promise.all(promises);
}
_listeners(target, type, unwrap) {
const events = target._events;
if (events === void 0)
return [];
const listener = events[type];
if (listener === void 0)
return [];
if (typeof listener === "function")
return unwrap ? [unwrapListener(listener)] : [listener];
return unwrap ? unwrapListeners(listener) : listener.slice();
}
}
function checkListener(listener) {
if (typeof listener !== "function")
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
}
class OnceWrapper {
constructor(eventEmitter, eventType, listener) {
this._fired = false;
this._eventEmitter = eventEmitter;
this._eventType = eventType;
this._listener = listener;
this.wrapperFunction = this._handle.bind(this);
this.wrapperFunction.listener = listener;
}
_handle(...args) {
if (this._fired)
return;
this._fired = true;
this._eventEmitter.removeListener(this._eventType, this.wrapperFunction);
return this._listener.apply(this._eventEmitter, args);
}
}
function unwrapListener(l) {
return wrappedListener(l) ?? l;
}
function unwrapListeners(arr) {
return arr.map((l) => wrappedListener(l) ?? l);
}
function wrappedListener(l) {
return l.listener;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
EventEmitter
});

View File

@@ -1,98 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var events_exports = {};
__export(events_exports, {
Events: () => Events
});
module.exports = __toCommonJS(events_exports);
const Events = {
AndroidDevice: {
WebView: "webview",
Close: "close"
},
AndroidSocket: {
Data: "data",
Close: "close"
},
AndroidWebView: {
Close: "close"
},
Browser: {
Disconnected: "disconnected"
},
BrowserContext: {
Console: "console",
Close: "close",
Dialog: "dialog",
Page: "page",
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
WebError: "weberror",
BackgroundPage: "backgroundpage",
ServiceWorker: "serviceworker",
Request: "request",
Response: "response",
RequestFailed: "requestfailed",
RequestFinished: "requestfinished"
},
BrowserServer: {
Close: "close"
},
Page: {
Close: "close",
Crash: "crash",
Console: "console",
Dialog: "dialog",
Download: "download",
FileChooser: "filechooser",
DOMContentLoaded: "domcontentloaded",
// Can't use just 'error' due to node.js special treatment of error events.
// @see https://nodejs.org/api/events.html#events_error_events
PageError: "pageerror",
Request: "request",
Response: "response",
RequestFailed: "requestfailed",
RequestFinished: "requestfinished",
FrameAttached: "frameattached",
FrameDetached: "framedetached",
FrameNavigated: "framenavigated",
Load: "load",
Popup: "popup",
WebSocket: "websocket",
Worker: "worker"
},
WebSocket: {
Close: "close",
Error: "socketerror",
FrameReceived: "framereceived",
FrameSent: "framesent"
},
Worker: {
Close: "close"
},
ElectronApplication: {
Close: "close",
Console: "console",
Window: "window"
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Events
});

View File

@@ -1,367 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var fetch_exports = {};
__export(fetch_exports, {
APIRequest: () => APIRequest,
APIRequestContext: () => APIRequestContext,
APIResponse: () => APIResponse
});
module.exports = __toCommonJS(fetch_exports);
var import_browserContext = require("./browserContext");
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_network = require("./network");
var import_tracing = require("./tracing");
var import_assert = require("../utils/isomorphic/assert");
var import_fileUtils = require("./fileUtils");
var import_headers = require("../utils/isomorphic/headers");
var import_rtti = require("../utils/isomorphic/rtti");
class APIRequest {
constructor(playwright) {
this._contexts = /* @__PURE__ */ new Set();
this._playwright = playwright;
}
async newContext(options = {}) {
options = {
...this._playwright._defaultContextOptions,
timeout: this._playwright._defaultContextTimeout,
...options
};
const storageState = typeof options.storageState === "string" ? JSON.parse(await this._playwright._platform.fs().promises.readFile(options.storageState, "utf8")) : options.storageState;
const context = APIRequestContext.from((await this._playwright._channel.newRequest({
...options,
extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
storageState,
tracesDir: this._playwright._defaultLaunchOptions?.tracesDir,
// We do not expose tracesDir in the API, so do not allow options to accidentally override it.
clientCertificates: await (0, import_browserContext.toClientCertificatesProtocol)(this._playwright._platform, options.clientCertificates)
})).request);
this._contexts.add(context);
context._request = this;
context._tracing._tracesDir = this._playwright._defaultLaunchOptions?.tracesDir;
await context._instrumentation.runAfterCreateRequestContext(context);
return context;
}
}
class APIRequestContext extends import_channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._tracing = import_tracing.Tracing.from(initializer.tracing);
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose(options = {}) {
this._closeReason = options.reason;
await this._instrumentation.runBeforeCloseRequestContext(this);
try {
await this._channel.dispose(options);
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
this._tracing._resetStackCounter();
this._request?._contexts.delete(this);
}
async delete(url, options) {
return await this.fetch(url, {
...options,
method: "DELETE"
});
}
async head(url, options) {
return await this.fetch(url, {
...options,
method: "HEAD"
});
}
async get(url, options) {
return await this.fetch(url, {
...options,
method: "GET"
});
}
async patch(url, options) {
return await this.fetch(url, {
...options,
method: "PATCH"
});
}
async post(url, options) {
return await this.fetch(url, {
...options,
method: "POST"
});
}
async put(url, options) {
return await this.fetch(url, {
...options,
method: "PUT"
});
}
async fetch(urlOrRequest, options = {}) {
const url = (0, import_rtti.isString)(urlOrRequest) ? urlOrRequest : void 0;
const request = (0, import_rtti.isString)(urlOrRequest) ? void 0 : urlOrRequest;
return await this._innerFetch({ url, request, ...options });
}
async _innerFetch(options = {}) {
return await this._wrapApiCall(async () => {
if (this._closeReason)
throw new import_errors.TargetClosedError(this._closeReason);
(0, import_assert.assert)(options.request || typeof options.url === "string", "First argument must be either URL string or Request");
(0, import_assert.assert)((options.data === void 0 ? 0 : 1) + (options.form === void 0 ? 0 : 1) + (options.multipart === void 0 ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
(0, import_assert.assert)(options.maxRedirects === void 0 || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
(0, import_assert.assert)(options.maxRetries === void 0 || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
const url = options.url !== void 0 ? options.url : options.request.url();
const method = options.method || options.request?.method();
let encodedParams = void 0;
if (typeof options.params === "string")
encodedParams = options.params;
else if (options.params instanceof URLSearchParams)
encodedParams = options.params.toString();
const headersObj = options.headers || options.request?.headers();
const headers = headersObj ? (0, import_headers.headersObjectToArray)(headersObj) : void 0;
let jsonData;
let formData;
let multipartData;
let postDataBuffer;
if (options.data !== void 0) {
if ((0, import_rtti.isString)(options.data)) {
if (isJsonContentType(headers))
jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);
else
postDataBuffer = Buffer.from(options.data, "utf8");
} else if (Buffer.isBuffer(options.data)) {
postDataBuffer = options.data;
} else if (typeof options.data === "object" || typeof options.data === "number" || typeof options.data === "boolean") {
jsonData = JSON.stringify(options.data);
} else {
throw new Error(`Unexpected 'data' type`);
}
} else if (options.form) {
if (globalThis.FormData && options.form instanceof FormData) {
formData = [];
for (const [name, value] of options.form.entries()) {
if (typeof value !== "string")
throw new Error(`Expected string for options.form["${name}"], found File. Please use options.multipart instead.`);
formData.push({ name, value });
}
} else {
formData = objectToArray(options.form);
}
} else if (options.multipart) {
multipartData = [];
if (globalThis.FormData && options.multipart instanceof FormData) {
const form = options.multipart;
for (const [name, value] of form.entries()) {
if ((0, import_rtti.isString)(value)) {
multipartData.push({ name, value });
} else {
const file = {
name: value.name,
mimeType: value.type,
buffer: Buffer.from(await value.arrayBuffer())
};
multipartData.push({ name, file });
}
}
} else {
for (const [name, value] of Object.entries(options.multipart))
multipartData.push(await toFormField(this._platform, name, value));
}
}
if (postDataBuffer === void 0 && jsonData === void 0 && formData === void 0 && multipartData === void 0)
postDataBuffer = options.request?.postDataBuffer() || void 0;
const fixtures = {
__testHookLookup: options.__testHookLookup
};
const result = await this._channel.fetch({
url,
params: typeof options.params === "object" ? objectToArray(options.params) : void 0,
encodedParams,
method,
headers,
postData: postDataBuffer,
jsonData,
formData,
multipartData,
timeout: options.timeout,
failOnStatusCode: options.failOnStatusCode,
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
maxRedirects: options.maxRedirects,
maxRetries: options.maxRetries,
...fixtures
});
return new APIResponse(this, result.response);
});
}
async storageState(options = {}) {
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) {
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, options.path);
await this._platform.fs().promises.writeFile(options.path, JSON.stringify(state, void 0, 2), "utf8");
}
return state;
}
}
async function toFormField(platform, name, value) {
const typeOfValue = typeof value;
if (isFilePayload(value)) {
const payload = value;
if (!Buffer.isBuffer(payload.buffer))
throw new Error(`Unexpected buffer type of 'data.${name}'`);
return { name, file: filePayloadToJson(payload) };
} else if (typeOfValue === "string" || typeOfValue === "number" || typeOfValue === "boolean") {
return { name, value: String(value) };
} else {
return { name, file: await readStreamToJson(platform, value) };
}
}
function isJsonParsable(value) {
if (typeof value !== "string")
return false;
try {
JSON.parse(value);
return true;
} catch (e) {
if (e instanceof SyntaxError)
return false;
else
throw e;
}
}
class APIResponse {
constructor(context, initializer) {
this._request = context;
this._initializer = initializer;
this._headers = new import_network.RawHeaders(this._initializer.headers);
if (context._platform.inspectCustom)
this[context._platform.inspectCustom] = () => this._inspect();
}
ok() {
return this._initializer.status >= 200 && this._initializer.status <= 299;
}
url() {
return this._initializer.url;
}
status() {
return this._initializer.status;
}
statusText() {
return this._initializer.statusText;
}
headers() {
return this._headers.headers();
}
headersArray() {
return this._headers.headersArray();
}
async body() {
return await this._request._wrapApiCall(async () => {
try {
const result = await this._request._channel.fetchResponseBody({ fetchUid: this._fetchUid() });
if (result.binary === void 0)
throw new Error("Response has been disposed");
return result.binary;
} catch (e) {
if ((0, import_errors.isTargetClosedError)(e))
throw new Error("Response has been disposed");
throw e;
}
}, true);
}
async text() {
const content = await this.body();
return content.toString("utf8");
}
async json() {
const content = await this.text();
return JSON.parse(content);
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose() {
await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() });
}
_inspect() {
const headers = this.headersArray().map(({ name, value }) => ` ${name}: ${value}`);
return `APIResponse: ${this.status()} ${this.statusText()}
${headers.join("\n")}`;
}
_fetchUid() {
return this._initializer.fetchUid;
}
async _fetchLog() {
const { log } = await this._request._channel.fetchLog({ fetchUid: this._fetchUid() });
return log;
}
}
function filePayloadToJson(payload) {
return {
name: payload.name,
mimeType: payload.mimeType,
buffer: payload.buffer
};
}
async function readStreamToJson(platform, stream) {
const buffer = await new Promise((resolve, reject) => {
const chunks = [];
stream.on("data", (chunk) => chunks.push(chunk));
stream.on("end", () => resolve(Buffer.concat(chunks)));
stream.on("error", (err) => reject(err));
});
const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString("utf8") : stream.path;
return {
name: platform.path().basename(streamPath),
buffer
};
}
function isJsonContentType(headers) {
if (!headers)
return false;
for (const { name, value } of headers) {
if (name.toLocaleLowerCase() === "content-type")
return value === "application/json";
}
return false;
}
function objectToArray(map) {
if (!map)
return void 0;
const result = [];
for (const [name, value] of Object.entries(map)) {
if (value !== void 0)
result.push({ name, value: String(value) });
}
return result;
}
function isFilePayload(value) {
return typeof value === "object" && value["name"] && value["mimeType"] && value["buffer"];
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
APIRequest,
APIRequestContext,
APIResponse
});

View File

@@ -1,46 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var fileChooser_exports = {};
__export(fileChooser_exports, {
FileChooser: () => FileChooser
});
module.exports = __toCommonJS(fileChooser_exports);
class FileChooser {
constructor(page, elementHandle, isMultiple) {
this._page = page;
this._elementHandle = elementHandle;
this._isMultiple = isMultiple;
}
element() {
return this._elementHandle;
}
isMultiple() {
return this._isMultiple;
}
page() {
return this._page;
}
async setFiles(files, options) {
return await this._elementHandle.setInputFiles(files, options);
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FileChooser
});

View File

@@ -1,386 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var frame_exports = {};
__export(frame_exports, {
Frame: () => Frame,
verifyLoadState: () => verifyLoadState
});
module.exports = __toCommonJS(frame_exports);
var import_eventEmitter = require("./eventEmitter");
var import_channelOwner = require("./channelOwner");
var import_clientHelper = require("./clientHelper");
var import_elementHandle = require("./elementHandle");
var import_events = require("./events");
var import_jsHandle = require("./jsHandle");
var import_locator = require("./locator");
var network = __toESM(require("./network"));
var import_types = require("./types");
var import_waiter = require("./waiter");
var import_assert = require("../utils/isomorphic/assert");
var import_locatorUtils = require("../utils/isomorphic/locatorUtils");
var import_urlMatch = require("../utils/isomorphic/urlMatch");
class Frame extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._parentFrame = null;
this._url = "";
this._name = "";
this._detached = false;
this._childFrames = /* @__PURE__ */ new Set();
this._eventEmitter = new import_eventEmitter.EventEmitter(parent._platform);
this._eventEmitter.setMaxListeners(0);
this._parentFrame = Frame.fromNullable(initializer.parentFrame);
if (this._parentFrame)
this._parentFrame._childFrames.add(this);
this._name = initializer.name;
this._url = initializer.url;
this._loadStates = new Set(initializer.loadStates);
this._channel.on("loadstate", (event) => {
if (event.add) {
this._loadStates.add(event.add);
this._eventEmitter.emit("loadstate", event.add);
}
if (event.remove)
this._loadStates.delete(event.remove);
if (!this._parentFrame && event.add === "load" && this._page)
this._page.emit(import_events.Events.Page.Load, this._page);
if (!this._parentFrame && event.add === "domcontentloaded" && this._page)
this._page.emit(import_events.Events.Page.DOMContentLoaded, this._page);
});
this._channel.on("navigated", (event) => {
this._url = event.url;
this._name = event.name;
this._eventEmitter.emit("navigated", event);
if (!event.error && this._page)
this._page.emit(import_events.Events.Page.FrameNavigated, this);
});
}
static from(frame) {
return frame._object;
}
static fromNullable(frame) {
return frame ? Frame.from(frame) : null;
}
page() {
return this._page;
}
async goto(url, options = {}) {
const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
return network.Response.fromNullable((await this._channel.goto({ url, ...options, waitUntil })).response);
}
_setupNavigationWaiter(options) {
const waiter = new import_waiter.Waiter(this._page, "");
if (this._page.isClosed())
waiter.rejectImmediately(this._page._closeErrorWithReason());
waiter.rejectOnEvent(this._page, import_events.Events.Page.Close, () => this._page._closeErrorWithReason());
waiter.rejectOnEvent(this._page, import_events.Events.Page.Crash, new Error("Navigation failed because page crashed!"));
waiter.rejectOnEvent(this._page, import_events.Events.Page.FrameDetached, new Error("Navigating frame was detached!"), (frame) => frame === this);
const timeout = this._page._timeoutSettings.navigationTimeout(options);
waiter.rejectOnTimeout(timeout, `Timeout ${timeout}ms exceeded.`);
return waiter;
}
async waitForNavigation(options = {}) {
return await this._page._wrapApiCall(async () => {
const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
const waiter = this._setupNavigationWaiter(options);
const toUrl = typeof options.url === "string" ? ` to "${options.url}"` : "";
waiter.log(`waiting for navigation${toUrl} until "${waitUntil}"`);
const navigatedEvent = await waiter.waitForEvent(this._eventEmitter, "navigated", (event) => {
if (event.error)
return true;
waiter.log(` navigated to "${event.url}"`);
return (0, import_urlMatch.urlMatches)(this._page?.context()._options.baseURL, event.url, options.url);
});
if (navigatedEvent.error) {
const e = new Error(navigatedEvent.error);
e.stack = "";
await waiter.waitForPromise(Promise.reject(e));
}
if (!this._loadStates.has(waitUntil)) {
await waiter.waitForEvent(this._eventEmitter, "loadstate", (s) => {
waiter.log(` "${s}" event fired`);
return s === waitUntil;
});
}
const request = navigatedEvent.newDocument ? network.Request.fromNullable(navigatedEvent.newDocument.request) : null;
const response = request ? await waiter.waitForPromise(request._finalRequest()._internalResponse()) : null;
waiter.dispose();
return response;
});
}
async waitForLoadState(state = "load", options = {}) {
state = verifyLoadState("state", state);
return await this._page._wrapApiCall(async () => {
const waiter = this._setupNavigationWaiter(options);
if (this._loadStates.has(state)) {
waiter.log(` not waiting, "${state}" event already fired`);
} else {
await waiter.waitForEvent(this._eventEmitter, "loadstate", (s) => {
waiter.log(` "${s}" event fired`);
return s === state;
});
}
waiter.dispose();
});
}
async waitForURL(url, options = {}) {
if ((0, import_urlMatch.urlMatches)(this._page?.context()._options.baseURL, this.url(), url))
return await this.waitForLoadState(options.waitUntil, options);
await this.waitForNavigation({ url, ...options });
}
async frameElement() {
return import_elementHandle.ElementHandle.from((await this._channel.frameElement()).element);
}
async evaluateHandle(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return import_jsHandle.JSHandle.from(result.handle);
}
async evaluate(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async _evaluateExposeUtilityScript(pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async $(selector, options) {
const result = await this._channel.querySelector({ selector, ...options });
return import_elementHandle.ElementHandle.fromNullable(result.element);
}
async waitForSelector(selector, options = {}) {
if (options.visibility)
throw new Error("options.visibility is not supported, did you mean options.state?");
if (options.waitFor && options.waitFor !== "visible")
throw new Error("options.waitFor is not supported, did you mean options.state?");
const result = await this._channel.waitForSelector({ selector, ...options });
return import_elementHandle.ElementHandle.fromNullable(result.element);
}
async dispatchEvent(selector, type, eventInit, options = {}) {
await this._channel.dispatchEvent({ selector, type, eventInit: (0, import_jsHandle.serializeArgument)(eventInit), ...options });
}
async $eval(selector, pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
const result = await this._channel.evalOnSelector({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async $$eval(selector, pageFunction, arg) {
(0, import_jsHandle.assertMaxArguments)(arguments.length, 3);
const result = await this._channel.evalOnSelectorAll({ selector, expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: (0, import_jsHandle.serializeArgument)(arg) });
return (0, import_jsHandle.parseResult)(result.value);
}
async $$(selector) {
const result = await this._channel.querySelectorAll({ selector });
return result.elements.map((e) => import_elementHandle.ElementHandle.from(e));
}
async _queryCount(selector) {
return (await this._channel.queryCount({ selector })).value;
}
async content() {
return (await this._channel.content()).value;
}
async setContent(html, options = {}) {
const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
await this._channel.setContent({ html, ...options, waitUntil });
}
name() {
return this._name || "";
}
url() {
return this._url;
}
parentFrame() {
return this._parentFrame;
}
childFrames() {
return Array.from(this._childFrames);
}
isDetached() {
return this._detached;
}
async addScriptTag(options = {}) {
const copy = { ...options };
if (copy.path) {
copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
copy.content = (0, import_clientHelper.addSourceUrlToScript)(copy.content, copy.path);
}
return import_elementHandle.ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element);
}
async addStyleTag(options = {}) {
const copy = { ...options };
if (copy.path) {
copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
copy.content += "/*# sourceURL=" + copy.path.replace(/\n/g, "") + "*/";
}
return import_elementHandle.ElementHandle.from((await this._channel.addStyleTag({ ...copy })).element);
}
async click(selector, options = {}) {
return await this._channel.click({ selector, ...options });
}
async dblclick(selector, options = {}) {
return await this._channel.dblclick({ selector, ...options });
}
async dragAndDrop(source, target, options = {}) {
return await this._channel.dragAndDrop({ source, target, ...options });
}
async tap(selector, options = {}) {
return await this._channel.tap({ selector, ...options });
}
async fill(selector, value, options = {}) {
return await this._channel.fill({ selector, value, ...options });
}
async _highlight(selector) {
return await this._channel.highlight({ selector });
}
locator(selector, options) {
return new import_locator.Locator(this, selector, options);
}
getByTestId(testId) {
return this.locator((0, import_locatorUtils.getByTestIdSelector)((0, import_locator.testIdAttributeName)(), testId));
}
getByAltText(text, options) {
return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
}
frameLocator(selector) {
return new import_locator.FrameLocator(this, selector);
}
async focus(selector, options = {}) {
await this._channel.focus({ selector, ...options });
}
async textContent(selector, options = {}) {
const value = (await this._channel.textContent({ selector, ...options })).value;
return value === void 0 ? null : value;
}
async innerText(selector, options = {}) {
return (await this._channel.innerText({ selector, ...options })).value;
}
async innerHTML(selector, options = {}) {
return (await this._channel.innerHTML({ selector, ...options })).value;
}
async getAttribute(selector, name, options = {}) {
const value = (await this._channel.getAttribute({ selector, name, ...options })).value;
return value === void 0 ? null : value;
}
async inputValue(selector, options = {}) {
return (await this._channel.inputValue({ selector, ...options })).value;
}
async isChecked(selector, options = {}) {
return (await this._channel.isChecked({ selector, ...options })).value;
}
async isDisabled(selector, options = {}) {
return (await this._channel.isDisabled({ selector, ...options })).value;
}
async isEditable(selector, options = {}) {
return (await this._channel.isEditable({ selector, ...options })).value;
}
async isEnabled(selector, options = {}) {
return (await this._channel.isEnabled({ selector, ...options })).value;
}
async isHidden(selector, options = {}) {
return (await this._channel.isHidden({ selector, ...options })).value;
}
async isVisible(selector, options = {}) {
return (await this._channel.isVisible({ selector, ...options })).value;
}
async hover(selector, options = {}) {
await this._channel.hover({ selector, ...options });
}
async selectOption(selector, values, options = {}) {
return (await this._channel.selectOption({ selector, ...(0, import_elementHandle.convertSelectOptionValues)(values), ...options })).values;
}
async setInputFiles(selector, files, options = {}) {
const converted = await (0, import_elementHandle.convertInputFiles)(this._platform, files, this.page().context());
await this._channel.setInputFiles({ selector, ...converted, ...options });
}
async type(selector, text, options = {}) {
await this._channel.type({ selector, text, ...options });
}
async press(selector, key, options = {}) {
await this._channel.press({ selector, key, ...options });
}
async check(selector, options = {}) {
await this._channel.check({ selector, ...options });
}
async uncheck(selector, options = {}) {
await this._channel.uncheck({ selector, ...options });
}
async setChecked(selector, checked, options) {
if (checked)
await this.check(selector, options);
else
await this.uncheck(selector, options);
}
async waitForTimeout(timeout) {
await this._channel.waitForTimeout({ timeout });
}
async waitForFunction(pageFunction, arg, options = {}) {
if (typeof options.polling === "string")
(0, import_assert.assert)(options.polling === "raf", "Unknown polling option: " + options.polling);
const result = await this._channel.waitForFunction({
...options,
pollingInterval: options.polling === "raf" ? void 0 : options.polling,
expression: String(pageFunction),
isFunction: typeof pageFunction === "function",
arg: (0, import_jsHandle.serializeArgument)(arg)
});
return import_jsHandle.JSHandle.from(result.handle);
}
async title() {
return (await this._channel.title()).value;
}
}
function verifyLoadState(name, waitUntil) {
if (waitUntil === "networkidle0")
waitUntil = "networkidle";
if (!import_types.kLifecycleEvents.has(waitUntil))
throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
return waitUntil;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Frame,
verifyLoadState
});

View File

@@ -1,87 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var harRouter_exports = {};
__export(harRouter_exports, {
HarRouter: () => HarRouter
});
module.exports = __toCommonJS(harRouter_exports);
class HarRouter {
static async create(localUtils, file, notFoundAction, options) {
const { harId, error } = await localUtils.harOpen({ file });
if (error)
throw new Error(error);
return new HarRouter(localUtils, harId, notFoundAction, options);
}
constructor(localUtils, harId, notFoundAction, options) {
this._localUtils = localUtils;
this._harId = harId;
this._options = options;
this._notFoundAction = notFoundAction;
}
async _handle(route) {
const request = route.request();
const response = await this._localUtils.harLookup({
harId: this._harId,
url: request.url(),
method: request.method(),
headers: await request.headersArray(),
postData: request.postDataBuffer() || void 0,
isNavigationRequest: request.isNavigationRequest()
});
if (response.action === "redirect") {
route._platform.log("api", `HAR: ${route.request().url()} redirected to ${response.redirectURL}`);
await route._redirectNavigationRequest(response.redirectURL);
return;
}
if (response.action === "fulfill") {
if (response.status === -1)
return;
await route.fulfill({
status: response.status,
headers: Object.fromEntries(response.headers.map((h) => [h.name, h.value])),
body: response.body
});
return;
}
if (response.action === "error")
route._platform.log("api", "HAR: " + response.message);
if (this._notFoundAction === "abort") {
await route.abort();
return;
}
await route.fallback();
}
async addContextRoute(context) {
await context.route(this._options.urlMatch || "**/*", (route) => this._handle(route));
}
async addPageRoute(page) {
await page.route(this._options.urlMatch || "**/*", (route) => this._handle(route));
}
async [Symbol.asyncDispose]() {
await this.dispose();
}
dispose() {
this._localUtils.harClose({ harId: this._harId }).catch(() => {
});
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
HarRouter
});

View File

@@ -1,82 +0,0 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var input_exports = {};
__export(input_exports, {
Keyboard: () => Keyboard,
Mouse: () => Mouse,
Touchscreen: () => Touchscreen
});
module.exports = __toCommonJS(input_exports);
class Keyboard {
constructor(page) {
this._page = page;
}
async down(key) {
await this._page._channel.keyboardDown({ key });
}
async up(key) {
await this._page._channel.keyboardUp({ key });
}
async insertText(text) {
await this._page._channel.keyboardInsertText({ text });
}
async type(text, options = {}) {
await this._page._channel.keyboardType({ text, ...options });
}
async press(key, options = {}) {
await this._page._channel.keyboardPress({ key, ...options });
}
}
class Mouse {
constructor(page) {
this._page = page;
}
async move(x, y, options = {}) {
await this._page._channel.mouseMove({ x, y, ...options });
}
async down(options = {}) {
await this._page._channel.mouseDown({ ...options });
}
async up(options = {}) {
await this._page._channel.mouseUp(options);
}
async click(x, y, options = {}) {
await this._page._channel.mouseClick({ x, y, ...options });
}
async dblclick(x, y, options = {}) {
await this.click(x, y, { ...options, clickCount: 2 });
}
async wheel(deltaX, deltaY) {
await this._page._channel.mouseWheel({ deltaX, deltaY });
}
}
class Touchscreen {
constructor(page) {
this._page = page;
}
async tap(x, y) {
await this._page._channel.touchscreenTap({ x, y });
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Keyboard,
Mouse,
Touchscreen
});

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