Compare commits

..

276 Commits
1.0.0 ... 1.5.1

Author SHA1 Message Date
998d706bf7 ci: update release workflow 2025-05-13 01:02:01 -07:00
f4a394bd3b feat: add notice every 30 minutes for next data refresh
every 30 minutes, console will print date on when next data refresh is
2025-05-12 16:57:48 -07:00
898bbe4827 docs(readme): update env variable list 2025-05-12 13:33:52 -07:00
66b69d5629 build: bump version from 1.5.0 to 1.5.1 2025-05-12 12:55:35 -07:00
818729d6ed feat: add automatic cron support; resync every 3 days
refresh iptv data every 3rd day at midnight
2025-05-12 12:55:08 -07:00
33a2a90eb1 build(deps): bump user-agents from 1.1.529 to 1.1.537 2025-05-12 12:54:04 -07:00
2ff6c193c9 build(deps): add package node-cron 2025-05-12 12:51:33 -07:00
647ce980b4 chore: update log level trace 2025-05-04 20:04:37 -07:00
f557c5aff6 feat: add status check for iptv services 2025-05-04 18:11:28 -07:00
2e67915bb6 refactor: add logging to promises 2025-05-04 16:00:03 -07:00
cce142d636 fix: page not found error on initial loading of /channels 2025-05-04 15:28:18 -07:00
e398e1acc4 refactor: log category labels 2025-05-04 13:29:44 -07:00
5c6756d98f feat: add new log type log.verbose with id 6 2025-05-04 13:29:02 -07:00
f0ccd04718 feat: add env var WEB_FOLDER 2025-05-04 13:27:12 -07:00
fe0545efc0 fix(deps): update dependency playwright to v1.52.0 2025-05-04 02:12:41 -07:00
15bfc1c35e fix(deps): update dependency user-agents to v1.1.529 2025-05-04 02:11:28 -07:00
a0635658bd chore(deps): update dependency @stylistic/eslint-plugin-js to v4 2025-05-04 02:09:56 -07:00
cde7a1ab40 fix: undefined variables 2025-05-04 01:52:13 -07:00
c8d68bdb2a feat: add detailed logging system 2025-05-04 01:47:01 -07:00
d18e67881c docs(CONTRIBUTING): remove <sub><sup> 2025-05-03 16:04:26 -07:00
cdfccdd022 build(dockerfile): add package curl to apk add 2025-05-03 16:01:13 -07:00
44f6a002bf docs(readme): add env var WEB_PROXY_HEADER 2025-05-03 16:00:41 -07:00
b85db52c77 docs(readme): update 2025-05-03 15:34:55 -07:00
3ebc986132 ci: update labels-create workflow 2025-05-03 15:14:15 -07:00
5973508f80 ci: update issues workflow 2025-05-03 04:13:41 -07:00
9a9e4ebedf fix: invalid api key should stop request once detected 2025-05-02 16:58:42 -07:00
a78e3201cb fix: ensure env var streamQuality casing is not sensitive 2025-05-02 16:57:36 -07:00
fbdeea47eb ci: update labels-create workflow 2025-05-02 04:04:01 -07:00
761a4f187e ci: update labels-clean workflow 2025-05-02 04:02:26 -07:00
77bb68e3a9 ci: update issues-stable workflow 2025-05-02 04:01:19 -07:00
189d913567 ci: update issues-scan workflow 2025-05-02 03:54:42 -07:00
2e9fdcf9bd ci: update workflow issues-new 2025-05-02 03:45:57 -07:00
f57a46cd29 ci: add workflow history-clean 2025-05-02 03:41:47 -07:00
305abb3e47 ci: update deploy-clean workflow 2025-05-02 03:39:36 -07:00
renovate[bot]
ef904032d7 chore(deps): lock file maintenance (#49)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 06:05:29 +00:00
renovate[bot]
55c1058832 chore(deps): lock file maintenance (#46)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-14 05:50:08 +00:00
879a9a658c docs: add docker run page 2025-04-12 00:56:33 -07:00
893679c843 feat: report client ip in logs for any connections made to server 2025-04-11 13:17:18 -07:00
f935b184d7 feat: add env variable API_KEY for restart / resync triggering 2025-04-11 13:03:19 -07:00
010a440e3e refactor: change toast notification lifetime from 4 to 10 seconds 2025-04-11 13:02:35 -07:00
51023a0e14 refactor: move api endpoint restart to api/restart 2025-04-11 13:01:22 -07:00
55d5cae85f docs: add main install page 2025-04-11 12:59:06 -07:00
d564f064d6 feat: add env variable WEB_PROXY_HEADER to override proxy header 2025-04-11 12:58:41 -07:00
1750b6ff11 docs: add healthcheck page 2025-04-11 07:45:38 -07:00
0690e1551b ci: add new argument release to release workflow 2025-04-10 04:34:04 -07:00
d1a7460c05 chore: update icon colors 2025-04-10 03:45:00 -07:00
70e349d7e3 chore: update health animation 2025-04-10 03:39:28 -07:00
568c3fc219 chore: add health check template var healthTimer 2025-04-10 03:00:04 -07:00
f55ecae8f3 feat: add heartbeat animation to header health icon 2025-04-10 02:56:39 -07:00
e4436ad7b7 feat: add healthcheck countdown as tooltip to header icon 2025-04-10 02:25:58 -07:00
67d7019a93 refactor: restyle console logs, add new api endpoints 2025-04-10 02:25:32 -07:00
4b45c0a2a2 feat: add release category to template 2025-04-10 02:22:38 -07:00
b3aae7b837 feat: add bootstrap tooltips 2025-04-10 02:19:48 -07:00
122286bd7b build: bump to v1.4.0 2025-04-10 02:17:58 -07:00
6708bb17a3 chore: set resync icon to continue animation until page refreshes 2025-04-09 09:12:23 -07:00
5fa7cd9d85 feat: new health api check every 10 minutes with toast notification to user 2025-04-09 09:00:33 -07:00
25ac27dd64 feat: add toast notifications 2025-04-09 09:00:12 -07:00
a659e03512 build: add new env variable HEALTH_TIMER
allows you to specify how often a health check is done
2025-04-09 08:59:38 -07:00
4d081adda2 docs(readme): update env definition 2025-04-08 18:08:41 -07:00
279d48d8ee feat: add api/health, json responses with info about each message thrown 2025-04-08 15:30:33 -07:00
c017578631 feat: add healthcheck api endpoint 2025-04-08 13:12:59 -07:00
f4baade73b build: create two new system env variables on app startup 2025-04-08 11:37:58 -07:00
f68053b461 build: add package express for webserver replacement 2025-04-08 10:00:16 -07:00
ec18ceb6db feat: add ability to restart / resync m3u playlist and epg guide data 2025-04-08 09:52:30 -07:00
68c4778ed8 refactor: rename vars with tar to gzp 2025-04-08 07:47:24 -07:00
cf28156e70 feat: new restart api route returns json 2025-04-08 07:46:32 -07:00
149fe89f89 build: add bootstrap v5.x 2025-04-08 07:41:50 -07:00
c124d93285 feat: add re-sync functionality / button to header 2025-04-08 03:21:46 -07:00
06e5d42c9c chore: update jellyfin accept-encoding comment block 2025-04-08 01:52:35 -07:00
c42b60a58c chore: add comment to new jellyfin encoding fix 2025-04-08 01:50:59 -07:00
1e8bdcddd8 fix: add header-encoding env variable to fix gzip compression for jellyfin xml grab
If gzip compression is enabled for the tvapp2 webserver, this may cause Jellyfin to fail when fetching newest xml guide data.

to correct this, we create an env variable which allows us to disable gzip compression for `Accept-Encoding`
2025-04-08 01:44:17 -07:00
90c2295bb8 docs(readme): update build version for commands 2025-04-08 00:57:41 -07:00
cca7b48d3b docs(readme): update build commands 2025-04-08 00:52:24 -07:00
2d9dec2d74 build: update package files 2025-04-08 00:20:51 -07:00
30741f124e refactor: update error logs 2025-04-08 00:17:01 -07:00
f256a9db06 Merge remote-tracking branch 'origin/main' 2025-04-07 23:01:27 -07:00
baf850308f fix: source 1 and 2 broken for streams 2025-04-07 23:01:12 -07:00
renovate[bot]
d9174d2a20 chore(deps): lock file maintenance (#44)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-07 07:27:00 +00:00
a5dad1e7bb docs(readme): update env variable list 2025-04-05 08:55:16 -07:00
aa4278e7a7 style: rename env variable FILE_PLAYLIST to FILE_M3U 2025-04-05 08:54:25 -07:00
4dfff72a9c build(dockerfile): add new env vars FILE_TAR and FILE_URL 2025-04-05 08:53:11 -07:00
624b24b1be build: bump app to v.1.2.0 2025-04-05 08:47:32 -07:00
c14f035147 refactor: m3u and epg files now stored in www folder 2025-04-05 08:41:14 -07:00
2d787f458d fix: html template links pointing to old repo 2025-04-05 08:40:13 -07:00
9169e5177e build: bump to v1.1.1 2025-04-05 07:25:36 -07:00
077c63d793 Merge remote-tracking branch 'origin/main' 2025-04-05 07:23:49 -07:00
2696fe05d4 feat: add support for additional mime types and default file type 2025-04-05 07:23:31 -07:00
3d042a3739 Merge pull request #37 from TheBinaryNinja/renovate/user-agents-1.x-lockfile
fix(deps): update dependency user-agents to v1.1.497
2025-04-02 10:21:05 -07:00
renovate[bot]
ad57bc5f78 fix(deps): update dependency user-agents to v1.1.497 2025-04-02 11:45:47 +00:00
renovate[bot]
5f2bb97d65 chore(deps): lock file maintenance (#41)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-31 07:37:16 +00:00
d9cb0edebe docs: update env variables 2025-03-28 14:02:49 -07:00
56c2bb2e4c ci: update documentation dependencies installed 2025-03-28 13:59:30 -07:00
3326468637 docs(mkdocs): add page: environment variables 2025-03-28 13:57:49 -07:00
23eeccb5e4 docs: add package external-markdown 2025-03-28 13:38:07 -07:00
7cd8e4af9b docs(changelog): update 2025-03-28 13:34:53 -07:00
a14aa856ab docs(mkdocs): update 2025-03-28 12:03:53 -07:00
d80d5e37c0 docs(mkdocs): update about.me 2025-03-26 06:53:04 -07:00
60c6924870 docs: add version alias 2025-03-26 05:01:23 -07:00
c3871cd542 docs: update 2025-03-26 04:56:44 -07:00
585051e324 Deployed 77d2252 to latest with MkDocs 1.6.1 and mike 2.1.3 2025-03-26 04:43:06 -07:00
77d22521c0 ci: update documentation workflow 2025-03-26 04:12:49 -07:00
5f1f9f7fff docs(mkdocs): update batch script 2025-03-26 04:05:08 -07:00
f8a2bdd464 ci: update .editorconfig headers 2025-03-26 02:21:29 -07:00
6e0c6c6ce0 ci: update contributors 2025-03-26 02:17:49 -07:00
8fd37af0d3 docs: update documentation 2025-03-26 02:12:57 -07:00
8f71f8195e docs(contributing): add package snippet to doc 2025-03-26 01:54:44 -07:00
1c75f11e9f docs(contributing): add new file 2025-03-26 01:44:20 -07:00
711f6b59f3 build(package): update build commands 2025-03-25 22:12:05 -07:00
568158484e ci: add npm run lint to release workflow process 2025-03-25 19:50:27 -07:00
81d17dd982 build(package): add new npm run lint command 2025-03-25 19:30:49 -07:00
07b34a7650 build(lint): update linting ignore list 2025-03-25 19:30:12 -07:00
10da0f4980 docs(readme): refactor keywords with icons 2025-03-25 18:40:41 -07:00
02591c5cff docs(readme): add development release screenshot and example 2025-03-25 17:59:05 -07:00
1675b9b751 docs(readme): update instructions for building docker container 2025-03-25 17:33:08 -07:00
6bf0c45277 docs(readme): update docker build instructions for new method 2025-03-25 15:55:20 -07:00
a76543d7a7 ci: clean up release workflow 2025-03-25 14:24:48 -07:00
e6d4cb7e4f ci: update release workflow summary 2025-03-25 14:13:52 -07:00
d567ea4517 ci: update release workflow output summary 2025-03-25 14:09:36 -07:00
424a4efb7a ci: update release workflow step to output changelogs 2025-03-25 14:05:23 -07:00
03d863f5e7 ci: update workflow env variables 2025-03-25 14:01:52 -07:00
820f770619 ci: update workflows 2025-03-25 13:59:57 -07:00
b6d5505c2d ci: fix variables 2025-03-25 13:53:11 -07:00
6af347f5d2 ci: modify workflow internal env management 2025-03-25 13:51:48 -07:00
eb0f7a5a72 ci: output workflow release changelogs 2025-03-25 13:32:49 -07:00
01415e13d5 ci: update env variables for release workflow 2025-03-25 13:26:55 -07:00
9329a3a032 ci: update release workflow 2025-03-25 12:43:20 -07:00
e857bfee03 fix: correct uuidv5 call for cjs to esm 2025-03-25 11:07:28 -07:00
7e71ff5e37 docs(readme): update env variables 2025-03-25 00:45:38 -07:00
bc629d6384 docs(readme): add screenshot 2025-03-25 00:42:30 -07:00
21d1c8bacf refactor: set primary gzip keyword to /gzip 2025-03-25 00:28:01 -07:00
f086e806df feat: add multiple url subpath keywords for assets 2025-03-25 00:27:23 -07:00
fdd9093f49 feat: change gzip compression from tar package to zlib 2025-03-24 23:27:02 -07:00
7fad9689c6 build(package): remove package tar 2025-03-24 23:26:10 -07:00
2b2795b7dd chore: remove fonts.min.css 2025-03-24 23:25:37 -07:00
fab2bc636a fix: initialization steps should wait on tar.gz promise to continue 2025-03-24 02:01:03 -07:00
f0237eb488 feat: add favicon; support for binary data 2025-03-24 00:01:48 -07:00
1160f43820 feat: add documentation icon to header of web ui 2025-03-23 22:57:09 -07:00
d7b2e338d9 feat: report human readable date in filelist for file mtime 2025-03-23 22:56:42 -07:00
2d61f8516d build(package): add documentation url 2025-03-23 22:55:54 -07:00
7c1483184a refactor: set header icons font-size clamp smaller 2025-03-23 22:55:32 -07:00
5f03e374d4 build(package): add new package moment.js 2025-03-23 22:41:41 -07:00
d391676a8e refactor: decrease font-size clamp maximum 2025-03-23 22:40:46 -07:00
25ed3c38e7 chore: removal of fonts.min.css - no longer needed 2025-03-23 22:38:03 -07:00
37195341c5 build: ensure app only installs production dependencies on docker deployment 2025-03-23 21:12:10 -07:00
6f0602f49b Merge remote-tracking branch 'origin/main' 2025-03-23 21:11:12 -07:00
b163dff842 feat: add filesize to list of files hosted in app 2025-03-23 21:10:07 -07:00
renovate[bot]
439b706060 chore(deps): lock file maintenance (#40)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-24 01:45:40 +00:00
44f7f8c6c2 refactor: variable names for xml and m3u 2025-03-23 18:44:06 -07:00
075303e9b6 feat: add xml.tar.gz compression during startup 2025-03-23 18:43:01 -07:00
62da08860d refactor: rename envFileEPG to envFileXML; add env var to rename 2025-03-23 05:31:51 -07:00
a6abfcde90 feat: add ejs template system 2025-03-23 03:57:23 -07:00
9f394e84cb chore: add debug logs 2025-03-23 03:36:53 -07:00
e0062d181d feat: add new template folder www 2025-03-23 03:27:00 -07:00
bc0ed61e1a build: update packages 2025-03-23 03:22:41 -07:00
6965d2eaa4 chore: template html migrated to independent file 2025-03-23 03:22:06 -07:00
016751ddbd chore: update script stylesheets 2025-03-23 03:21:23 -07:00
06bf294f8a feat: add new template system; index.html 2025-03-23 03:20:43 -07:00
23f2726ff8 build: add eslint flat config 2025-03-23 03:14:58 -07:00
9e54446f44 build: convert root.js from CJS to ESM 2025-03-23 03:14:21 -07:00
811380215a fix: viewport resizing on table content fitting screen 2025-03-22 05:51:56 -07:00
b4675dac70 style: minor edits to text ordering 2025-03-22 05:24:21 -07:00
d4df854820 feat: add link to xml, epg in new filelist table 2025-03-22 05:23:49 -07:00
94a55d5922 ci: update workflow 2025-03-22 01:29:23 -07:00
02f5d5e7d9 ci: test workflow build 2025-03-22 01:06:16 -07:00
f8ce387f4f ci: update workflow action docker/build-push-action network 2025-03-21 23:47:04 -07:00
8e01deba82 build: bump tvapp2 to v1.1.0 2025-03-21 13:57:33 -07:00
5dbf387bf0 docs(readme): add env vars to table 2025-03-21 05:10:56 -07:00
8a13954718 docs(readme): document newly added env variables 2025-03-21 04:50:18 -07:00
a05c4d3684 build(dockerfile): add new env variables 2025-03-21 04:50:00 -07:00
a6543fdc28 chore: set env LOG_LEVEL default to 4 2025-03-21 04:49:38 -07:00
23d4d523d6 chore: change css stylesheet for header 2025-03-21 04:16:00 -07:00
b6a8bb2e66 fix: make sure localhost warning box does not show if hidden 2025-03-21 03:09:09 -07:00
dea4a21f92 feat: new dark theme for webserver page 2025-03-21 02:16:36 -07:00
074fbe67d8 chore: remove cjs require in favor of new esm import method 2025-03-21 02:16:08 -07:00
eee25769c8 fix: add default value for env variable if running locally 2025-03-21 02:14:56 -07:00
cbef38f970 feat: add new env variable STREAM_QUALITY 2025-03-21 02:14:05 -07:00
03ab50f557 feat: add new env variables: FILE_PLAYLIST and FILE_EPG 2025-03-21 02:12:10 -07:00
6b88664db0 fix: server unable to be started via localhost; server ip var invalid 2025-03-21 02:09:58 -07:00
ceafd71461 feat: add new logging method; add env LOG_LEVEL 2025-03-21 02:09:07 -07:00
614a0a2daf build: convert code from CJS to ESM 2025-03-21 01:59:26 -07:00
e6bde3e0de chore: add tvapp2.min.js javascript 2025-03-20 20:05:54 -07:00
50c724869e chore: add tvapp2.min.css stylesheet 2025-03-20 20:01:44 -07:00
b595aa09a5 build(package): add type module for node esm 2025-03-20 19:08:31 -07:00
7e68e55ae6 build(dockerfile): fix arg ARCH being defined after initial pull 2025-03-20 19:06:42 -07:00
1a423d0def Merge pull request #36 from TheBinaryNinja/renovate/user-agents-1.x-lockfile
fix(deps): update dependency user-agents to v1.1.484
2025-03-20 00:41:36 -07:00
renovate[bot]
e107475252 fix(deps): update dependency user-agents to v1.1.484 2025-03-20 06:50:02 +00:00
667b260aff Merge pull request #32 from TheBinaryNinja/renovate/playwright-monorepo
fix(deps): update dependency playwright to v1.51.1
2025-03-19 10:18:03 -07:00
4036117d6c Merge pull request #35 from TheBinaryNinja/renovate/user-agents-1.x-lockfile
fix(deps): update dependency user-agents to v1.1.483
2025-03-19 10:17:33 -07:00
renovate[bot]
83ee35b9ae fix(deps): update dependency user-agents to v1.1.483 2025-03-19 12:33:41 +00:00
9cca602585 build: add healthcheck to docker-compose.yml 2025-03-17 22:59:31 -07:00
05944d284e build: bump base to alpine:3.21; add platform parameter 2025-03-17 22:36:28 -07:00
b8d6e4f538 ci: style workflows 2025-03-17 19:30:01 -07:00
a45541f0b9 ci: update workflow commenting 2025-03-17 18:49:10 -07:00
d836665df3 ci: minor refactoring of comment blocks to workflow issues-scan 2025-03-17 17:56:06 -07:00
21cb2a90c8 ci: add name to main job in workflow issues-scan 2025-03-17 17:54:11 -07:00
renovate[bot]
259f0a1fe1 fix(deps): update dependency playwright to v1.51.1 2025-03-18 00:44:02 +00:00
731ca00390 ci: update issues-scan workflow for compatibility with renovate bot 2025-03-17 17:42:42 -07:00
c938f7a212 ci: add debugging to github workflows 2025-03-17 16:32:13 -07:00
b11f85caf5 ci: add debugging for js context 2025-03-17 16:25:50 -07:00
0e0eefba2b ci: fix workflow pr issue 2025-03-17 16:22:03 -07:00
06cbd4663c ci: list existing labels for workflow 2025-03-17 12:57:56 -07:00
a578677137 ci: update labels for workflow 2025-03-17 12:52:26 -07:00
e5cecbac54 ci: get existing labels in workflow 2025-03-17 12:48:16 -07:00
42597e0652 ci: update issues-scan workflow 2025-03-17 12:39:39 -07:00
8ccd339d05 ci: change package manager node to nodenv 2025-03-17 12:22:48 -07:00
54afd779b3 ci: add node manager to renovate config 2025-03-17 12:19:33 -07:00
f83ef42653 ci: update renovate labels for packageRules 2025-03-17 12:11:47 -07:00
dc36741c72 ci: update renovate rangeStrategy to auto 2025-03-17 12:08:03 -07:00
2ec3b6ed19 ci: update renovate rangeStrategy to pin 2025-03-17 12:07:14 -07:00
77ae969d28 docs(readme): update expired discord invite link #8 2025-03-17 11:41:26 -07:00
f8bf7afbe2 ci: disable updatePinnedDependencies on renovate 2025-03-17 03:40:19 -07:00
aa61eeec34 ci: add gitAuthor to renovate 2025-03-17 03:35:12 -07:00
7cdb4b9109 ci: enable forkProcessing in renovate 2025-03-17 03:32:09 -07:00
99c0491cb5 ci: update renovate config 2025-03-17 03:28:37 -07:00
e889bb37b6 ci: update preserveSemverRanges for renovate 2025-03-17 03:20:46 -07:00
2db3ea8c62 ci: enable renovate dependency dashboard 2025-03-17 03:12:57 -07:00
d54303d0d7 Merge pull request #30 from TheBinaryNinja/renovate/user-agents-1.x
PR 30: fix(deps): update dependency user-agents to ^1.1.480
2025-03-17 03:00:31 -07:00
renovate[bot]
f6fa7054bd fix(deps): update dependency user-agents to ^1.1.480 2025-03-17 07:11:23 +00:00
renovate[bot]
6249d3ad42 chore(deps): lock file maintenance (#29)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-17 07:10:06 +00:00
da241a8c9e Merge pull request #27 from TheBinaryNinja/renovate/user-agents-1.x
fix(deps): update dependency user-agents to ^1.1.477
2025-03-16 20:51:45 -07:00
525dae72e1 Merge pull request #28 from TheBinaryNinja/renovate/tj-actions-changed-files-46.x
chore(deps): update tj-actions/changed-files action to v46
2025-03-16 16:30:02 -07:00
renovate[bot]
6182a1c3d4 chore(deps): update tj-actions/changed-files action to v46 2025-03-16 06:34:40 +00:00
renovate[bot]
a00aaa77cf fix(deps): update dependency user-agents to ^1.1.477 2025-03-14 12:00:29 +00:00
57aa72b355 Merge pull request #23 from TheBinaryNinja/renovate/playwright-monorepo
fix(deps): update dependency playwright to ^1.51.0
2025-03-13 01:20:26 -07:00
5ad42e6388 Merge pull request #25 from TheBinaryNinja/renovate/tsickert-discord-webhook-7.x
chore(deps): update tsickert/discord-webhook action to v7
2025-03-13 01:17:58 -07:00
renovate[bot]
d0b2f0a4f4 fix(deps): update dependency playwright to ^1.51.0 2025-03-13 08:17:55 +00:00
af90927a84 ci: update renovate config 2025-03-13 01:16:35 -07:00
5a5c0606a7 Merge pull request #22 from TheBinaryNinja/renovate/user-agents-1.x-lockfile
fix(deps): update dependency user-agents to v1.1.477
2025-03-13 01:03:50 -07:00
renovate[bot]
946469a4db chore(deps): update tsickert/discord-webhook action to v7 2025-03-13 08:03:43 +00:00
renovate[bot]
5aa8e1ad7e fix(deps): update dependency user-agents to v1.1.477 2025-03-13 08:00:11 +00:00
b29144be8f Merge pull request #21 from TheBinaryNinja/renovate/configure
chore: Configure Renovate
2025-03-13 00:58:51 -07:00
c13bb89584 chore: stage package-lock.json 2025-03-13 00:56:30 -07:00
renovate[bot]
aaed36e18b Add renovate.json 2025-03-13 07:56:18 +00:00
57f84e4bba chore: bump node version in dockerfile 2025-03-13 00:55:30 -07:00
8983cabd48 docs(readme): modify traefik instructions 2025-03-13 00:53:05 -07:00
8d856a30bd fix: correct typo default arch amd64 in Dockerfile 2025-03-02 04:33:33 -07:00
2518424ad1 build: add label architecture 2025-02-26 16:15:50 -07:00
f587291d72 ci: add workflow steps for digest exporting and readme extraction 2025-02-26 16:15:27 -07:00
b8dbba7a7e ci: update env variables for workflows 2025-02-26 14:22:57 -07:00
7d29ab31ce build: add org.opencontainers.image.registry 2025-02-26 07:36:30 -07:00
61345f3bcd ci: add org.opencontainers.image.registry 2025-02-26 07:36:16 -07:00
7c21adb843 ci: update tagging for deployment workflows 2025-02-26 05:42:25 -07:00
dd88789f14 ci: update github deployment workflows; add annotations list 2025-02-26 03:39:51 -07:00
4d8794d3d0 ci: add index-annotation to workflow action int128/docker-manifest-create-action 2025-02-26 02:46:35 -07:00
aa0028901a ci: update github deployment workflow; add annotations 2025-02-26 02:40:07 -07:00
901a11382d ci: add README.md cache to deployment workflow, inject docker label org.opencontainers.image.description` 2025-02-26 02:08:58 -07:00
9e19d9798f ci: add docker untagged image cleanup step 2025-02-26 01:55:15 -07:00
97f01942af ci: update deployment workflow to clean up untagged docker images 2025-02-26 01:48:33 -07:00
d622de1341 ci: update deployment workflow to add single and double digit version numbers 2025-02-26 01:38:17 -07:00
6e1ed71674 ci: set global flavor value to false, utilize tag instead 2025-02-25 12:25:57 -07:00
4ee2e79446 ci: remove global property flavor => latest in favor of labels for workflow docker/metadata-action 2025-02-25 11:20:38 -07:00
c70553093d ci: add REGISTRY_REPO_ORG_AUTHOR_LC and REGISTRY_REPO_AUTHOR_LC to remove uppercase conflict 2025-02-25 07:42:41 -07:00
57b0d44c7d chore: update docker image labels to conform with opencontainers spec
https://github.com/opencontainers/image-spec/blob/main/annotations.md
2025-02-25 06:41:37 -07:00
71fad013cf ci: add new build args VERSION BUILDDATE for action docker/build-push-action 2025-02-25 05:48:59 -07:00
d2aee89c21 build: update label BUILDVERSION syntax 2025-02-25 05:23:20 -07:00
544302de6e ci: replace env.ref_name with github.ref_name 2025-02-25 05:22:55 -07:00
dfdc44b1e3 ci: update deployment workflows to output debugging parameters during process 2025-02-25 05:05:22 -07:00
05ab495a46 ci: update gitea workflow to return output of parameters 2025-02-25 04:58:38 -07:00
f5829034a2 fix: workflow deploy-docker-all: up to 10 inputs for a workflow_dispatch event 2025-02-25 04:06:35 -07:00
f186dd8030 ci: update account token secret var for gitea.com registry 2025-02-25 04:02:29 -07:00
ec885559af ci: add docker deployment workflow for gitea.com registry 2025-02-25 03:55:05 -07:00
57c4547a66 ci: add IMAGE_GITEA_WEBSITE env var for dispatch workflow 2025-02-25 03:54:43 -07:00
f8a90a3e33 ci: fix issues-new and issues-stale to detect correct prefix in labels and not re-name 2025-02-25 03:09:37 -07:00
6700d94abb ci: create release meta as to not override existing sha256 before manifest merge completed 2025-02-24 18:32:17 -07:00
577b28cd6d ci: modify meta data generation for arm64 docker container for gitea 2025-02-24 18:16:09 -07:00
f5c767426f ci: modify arm64 workflow step tag to not conflict with amd64 before merge 2025-02-24 18:07:07 -07:00
9a9b18bfbc ci: modify gitea workflow image author and app name 2025-02-24 17:56:16 -07:00
a2aaddb9b2 build: update package.json with revised docker:build:arch commands 2025-02-24 16:55:42 -07:00
eac58e39aa docs(readme): update docker tags / registry list to show new structure of tags 2025-02-24 16:53:41 -07:00
6ec3894175 ci: add registry url to front of manifest paths before merge completed 2025-02-24 16:33:02 -07:00
f8b333af70 build: dockerfile and dockerfile.aarch64 merged, no longer needed 2025-02-24 16:25:52 -07:00
d021aaaea5 ci: merge amd64 and arm64 build process into single job 2025-02-24 16:25:09 -07:00
0a892a80ad ci: update deployment workflow metadata tags 2025-02-24 15:35:43 -07:00
17ecda57fe ci: remove requirement depending on deprecated job job-docker-release-dockerhub-arm64 2025-02-24 15:17:09 -07:00
4c0af665d5 ci: refactor dockerhub deployment process to handle both architectures in one step 2025-02-24 15:12:47 -07:00
3ef13657cd build: add arg $ARCH to Dockerfile and prep to combine architectures 2025-02-24 15:12:12 -07:00
455 changed files with 62887 additions and 51359 deletions

View File

@@ -3,7 +3,9 @@
"projectOwner": "Aetherinox",
"repoType": "github",
"repoHost": "https://github.com",
"files": ["README.md"],
"files": [
"README.md"
],
"imageSize": 40,
"commit": true,
"commitConvention": "angular",

View File

@@ -1,6 +1,7 @@
# #
# @file .editorconfig
# @author Aetherinox https://github.com/Aetherinox
# @author Aetherinox
# @repo https://github.com/TheBinaryNinja/tvapp2
# https://git.binaryninja.net/Aetherinox
# @ref http://editorconfig.org
# #

View File

@@ -1,25 +1,53 @@
# #
# @type github workflow
# @desc cleans up the list of deployments in the environment history
# edit the 'environment:' to determine which deployment to keep clean
# - can be ran manually
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage cleans up the list of deployments in the environment history
# edit the 'environment:' to determine which deployment to keep clean
# - can be ran manually
#
# @secrets secrets.SELF_TOKEN_CL Github Access Token (Classic)
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS Discord Webbhook URL; right-click on channel, click "Integrations"
# @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.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_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/deploy-clean.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/deploy-clean.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
# #
name: "⚙️ Deploy Clean"
run-name: "⚙️ Deploy Clean"
name: '🧹 Deployments Clean'
run-name: '🧹 Deployments Clean'
# #
# triggers
# #
on:
# #
# Trigger > Workflow Dispatch
# #
workflow_dispatch:
inputs:
@@ -55,68 +83,9 @@ env:
DEPLOYMENT_ENV: ${{ github.event.inputs.DEPLOYMENT_ENV || 'orion' }}
DEPLOYMENT_DELAY: ${{ github.event.inputs.DEPLOYMENT_DELAY || '1000' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
LABELS_JSON: |
[
{ "name": "AC Changes Made", "color": "8F1784", "description": "Requested changes have been made and are pending a re-scan" },
{ "name": "AC Changes Required", "color": "8F1784", "description": "Requires changes to be made to the package before being accepted" },
{ "name": "AC Failed", "color": "a61f2d", "description": "Autocheck failed to run through a complete cycle, requires investigation" },
{ "name": "AC Needs Rebase", "color": "8F1784", "description": "Due to the permissions on the requesting repo, this pull request must be rebased by the author" },
{ "name": "AC Passed", "color": "146b4a", "description": "Ready to be reviewed" },
{ "name": "AC Review Required", "color": "8F1784", "description": "PR needs to be reviewed by another person, after the requested changes have been made" },
{ "name": "AC Security Warning", "color": "761620", "description": "Does not conform to developer policies, or includes potentially dangerous code" },
{ "name": "AC Skipped Scan", "color": "8F1784", "description": "Author has skipped code scan" },
{ "name": "Status 𐄂 Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status 𐄂 Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status 𐄂 Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status 𐄂 Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status 𐄂 Locked", "color": "550F45", "description": "Automatically locked by EuropaServ for a prolonged period of inactivity" },
{ "name": "Status 𐄂 Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status 𐄂 No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status 𐄂 Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status 𐄂 Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status 𐄂 Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status 𐄂 Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 ◦ 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" },
{ "name": "Type ◦ Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type ◦ Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type ◦ Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build ◦ Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build ◦ Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build ◦ MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build ◦ Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build ◦ Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build ◦ Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": " API", "color": "F99B50", "description": "Plugin API, CLI, browser JS API" },
{ "name": " Auto-type", "color": "9141E0", "description": "Auto-type functionality in desktop apps" },
{ "name": " Browser", "color": "9141E0", "description": "Browser plugins and passing data to <=> from app" },
{ "name": " Customization", "color": "E3F0FC", "description": "Customizations: plugins, themes, configs" },
{ "name": " Design", "color": "FA70DE", "description": "Design related queries" },
{ "name": " Dist", "color": "FA70DE", "description": "Installers and other forms of software distribution" },
{ "name": " Enterprise", "color": "11447a", "description": "Issues about collaboration, administration, and so on" },
{ "name": " Hardware", "color": "5a7503", "description": "YubiKey, other tokens, biometrics" },
{ "name": " Import/Export", "color": "F5FFCC", "description": "Import from and export to different file formats" },
{ "name": " Improvement", "color": "185c98", "description": "Enhance an existing feature" },
{ "name": " Performance", "color": "006b75", "description": "Web and desktop performance issues" },
{ "name": " Plugin Request", "color": "FCE9CA", "description": "Requested changes should be implemented as a plugin" },
{ "name": " Security", "color": "F75D39", "description": "Security issues" },
{ "name": " Self-Hosting", "color": "fad8c7", "description": "Self-hosting installations and configs" },
{ "name": " Storage", "color": "5319e7", "description": "Storage providers: Dropbox, Google, WebDAV, etc." },
{ "name": " Updater", "color": "1BADDE", "description": "Auto-updater issues" },
{ "name": " UX", "color": "1BADDE", "description": "UX and usability" },
{ "name": " Website", "color": "fef2c0", "description": "Website related issues" },
{ "name": "⚠ Urgent", "color": "a8740e", "description": "Requires urgent attention" },
{ "name": "⚠ Announcement", "color": "DB4712", "description": "Announcements" },
{ "name": "📰 Progress Report", "color": "392297", "description": "Development updates" },
{ "name": "📦 Release", "color": "277542", "description": "Release announcements" },
{ "name": "✔️ Poll", "color": "972255", "description": "Community polls" },
{ "name": "❔ Question", "color": "FFFFFF", "description": "All questions" }
]
BOT_NAME_RENOVATE: renovate[bot]
# #
# jobs
@@ -124,9 +93,11 @@ env:
jobs:
cleanup:
name: >-
🧹 Deployments Clean
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 20
timeout-minutes: 5
permissions: write-all
steps:
@@ -139,6 +110,7 @@ jobs:
🕛 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
@@ -181,6 +153,7 @@ jobs:
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
# #
@@ -188,7 +161,7 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_cleanup_notify_discord_success
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
@@ -224,7 +197,7 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_cleanup_notify_discord_failure
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: 'Io'

File diff suppressed because it is too large Load Diff

View File

@@ -4,10 +4,31 @@
# @url https://github.com/Aetherinox
# @usage deploys docker container to Dockerhub and send message to discord
# upload this workflow to both the `main` branch of the tvapp2 repository
# @secrets secrets.ADMINSERV_GPG_KEY_ASC gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.ADMINSERV_GPG_PASSPHRASE gpg private key passphrase
# secrets.ORG_BINARYNINJA_DOCKERHUB_TOKEN github personal access token (classic) with package:write permission
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_RELEASES Discord webhook to report releases from github to discord
#
# @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.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
#
# @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/deploy-docker-dockerhub.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/deploy-docker-dockerhub.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "📦 Deploy Docker Dockerhub"
@@ -118,8 +139,11 @@ env:
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
IMAGE_DOCKERHUB_AUTHOR: ${{ github.event.inputs.IMAGE_DOCKERHUB_AUTHOR || 'thebinaryninja' }}
IMAGE_DOCKERHUB_USERNAME: ${{ github.event.inputs.IMAGE_DOCKERHUB_USERNAME || 'thebinaryninja' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# Jobs
@@ -203,282 +227,21 @@ jobs:
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
# #
# Job Docker Release Dockerhub Arm64
# Job Docker Release Dockerhub
# #
job-docker-release-dockerhub-arm64:
job-docker-release-dockerhub:
name: >-
📦 Release Dockerhub Arm64
📦 Release Dockerhub
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 10
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create ]
permissions:
contents: write
packages: write
attestations: write
id-token: write
steps:
# #
# Release Dockerhub Start Arm64
# #
- name: '🏳️ Start'
id: task_release_dh_start
run: |
echo "Starting Dockerhub arm64"
# #
# Release Dockerhub Checkout Amd64
# #
- name: '✅ Checkout'
id: task_release_gh_checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Dockerhub Get Timestamp
# #
- name: '🕛 Get Timestamp'
id: task_release_set_timestamp
run: |
echo "DOCKER_IMAGE=`echo ${{github.repository}} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV}
echo "DOCKER_SHA=${GITHUB_SHA}" >> $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 Dockerhub Install Dependencies
# #
- name: '📦 Install Dependencies'
id: task_release_dh_dependencies
run:
sudo apt-get install -qq dos2unix
# #
# Release Dockerhub Execute dos2unix
# #
- 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 --
echo "✅✅✅ Completed DOS2UNIX ✅✅✅"
# #
# Release Dockerhub Fix Permissions
# #
- 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)
if [ -n "${WRONG_PERM}" ]; then
echo "⚠️⚠️⚠️ Permissions are invalid ⚠️⚠️⚠️"
for i in ${WRONG_PERM}; do
echo "::error file=${i},line=1,title=Missing Executable Bit::This file needs to be set as executable!"
done
exit 1
else
echo "✅✅✅ Executable permissions are OK ✅✅✅"
fi
# #
# Release Dockerhub QEMU Arm64
# #
- name: '⚙️ Set up QEMU'
id: task_release_dh_qemu
uses: docker/setup-qemu-action@v3
# #
# Release Dockerhub Setup BuildX Arm64
# #
- name: '⚙️ Setup Buildx'
id: task_release_dh_buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
driver-opts: 'image=moby/buildkit:latest'
# #
# Release Dockerhub Registry Login Arm64
# #
- name: '⚙️ Login to Dockerhub'
id: task_release_dh_registry
uses: docker/login-action@v3
with:
username: ${{ env.IMAGE_DOCKERHUB_USERNAME }}
password: ${{ secrets.ORG_BINARYNINJA_DOCKERHUB_TOKEN }}
# #
# Release Dockerhub Meta Arm64
# #
- name: '🔨 Dockerhub: Meta - Arm64'
id: task_release_dh_meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_DOCKERHUB_AUTHOR }}/${{ env.IMAGE_NAME }}
tags: |
# latest no
type=raw,value=latest,enable=false
# tag add arm64
# type=raw,enable=true,priority=1000,value=arm64
# dispatch add x1.x.x-arm64
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=300,prefix=,suffix=-arm64,value=${{ env.IMAGE_VERSION }}
# dispatch add arm64-development
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-arm64,value=development
# tag add tag-arm64
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-arm64,event=tag
flavor: |
latest=false
labels: |
org.opencontainers.image.VERSION=${{ env.IMAGE_VERSION }}
org.opencontainers.image.BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.vendor=${{ env.IMAGE_DOCKERHUB_AUTHOR }}
org.opencontainers.image.ref.name=${{ env.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
# #
# Release Dockerhub Build and Push Arm64
# #
- name: '📦 Build & Push (linux/arm64)'
id: task_release_dh_push
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
context: .
file: Dockerfile.aarch64
platforms: linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.task_release_dh_meta.outputs.tags }}
labels: ${{ steps.task_release_dh_meta.outputs.labels }}
provenance: false
# #
# Release Dockerhub Checkpoint Arm64
# #
- name: '⚠️ Checkpoint'
id: task_release_dh_checkpoint
run: |
echo "registry ............. Dockerhub"
echo "github.actor.......... ${{ github.actor }}"
echo "github.ref ........... ${{ github.ref }}"
echo "github.ref_name ...... ${{ github.ref_name }}"
echo "github.event_name .... ${{ github.event_name }}"
echo "inputs.DRY_RUN ....... ${{ inputs.DRY_RUN }}"
echo "env.AUTHOR ........... ${{ env.IMAGE_DOCKERHUB_AUTHOR }}"
echo "tags ................. ${{ steps.task_release_dh_meta.outputs.tags }}"
echo "labels ............... ${{ steps.task_release_dh_meta.outputs.labels }}"
echo "docker image ......... ${{ env.DOCKER_IMAGE }}"
echo "docker sha ........... ${{ env.DOCKER_SHA }}"
echo "docker image id ...... ${{ steps.task_release_dh_push.outputs.imageid }}"
echo "docker digest ........ ${{ steps.task_release_dh_push.outputs.digest }}"
# #
# Release Dockerhub Get Weekly Commits
# #
- 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
echo 'EOF' >> $GITHUB_ENV
# #
# Release Dockerhub Notify Github
# #
- name: '🔔 Send Discord Webhook Message'
id: task_release_notifications_discord_send
uses: tsickert/discord-webhook@v6.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
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-description: |
## 📦 Docker Deploy (Dockerhub) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.IMAGE_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://github.com/${{ github.repository }}/pkgs/container/${{ env.IMAGE_NAME }}
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull ${{ env.DOCKER_IMAGE }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}-amd64`
- Pull: `docker pull ${{ env.DOCKER_IMAGE }}@${{ steps.task_release_dh_push.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' || '' }}`
- Version: `${{ env.IMAGE_VERSION }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && '✅ Successful' || '❌ Failed' }}`
### Tags
-# This docker image will use the following tags:
```
${{ steps.task_release_dh_meta.outputs.tags }}
```
### Labels
-# This docker image embeds the following labels:
```
${{ steps.task_release_dh_meta.outputs.labels }}
```
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
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"
# #
# Job Docker Release Dockerhub Amd64
# #
job-docker-release-dockerhub-amd64:
name: >-
📦 Release Dockerhub Amd64
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 10
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create, job-docker-release-dockerhub-arm64 ]
steps:
# #
@@ -507,12 +270,16 @@ jobs:
- name: '🕛 Get Timestamp'
id: task_release_set_timestamp
run: |
echo "DOCKER_IMAGE=`echo ${{github.repository}} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV}
echo "DOCKER_SHA=${GITHUB_SHA}" >> $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 "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
# #
# Release Dockerhub Install Dependencies
@@ -530,7 +297,9 @@ 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 --
echo "✅✅✅ Completed DOS2UNIX ✅✅✅"
# #
# Release Dockerhub Fix Permissions
@@ -582,50 +351,93 @@ jobs:
password: ${{ secrets.ORG_BINARYNINJA_DOCKERHUB_TOKEN }}
# #
# Release Dockerhub Meta Amd64
# Release Dockerhub Read Readme
#
# @usage org.opencontainers.image.description=${{ steps.task_release_dh_readme_cache.outputs.content }}
# #
- name: '🔨 Dockerhub: Meta - Amd64'
- name: '📄 Cache README.md'
id: task_release_dh_readme_cache
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ORG_BINARYNINJA_TOKEN_CL }}
script: |
'use strict'
const { promises: fs } = require('fs')
const main = async () => {
const path = "README.md"
let content = await fs.readFile(path, 'utf8')
core.setOutput('content', content)
}
main().catch(err => core.setFailed(err.message))
# #
# Release Dockerhub Meta
#
# this version of meta does not need one for amd64 and one for arm64 because both
# platforms are combined into one release, all sharing the same tags
# #
- name: '🔨 Dockerhub: Meta'
id: task_release_dh_meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_DOCKERHUB_AUTHOR }}/${{ env.IMAGE_NAME }}
${{ env.REGISTRY_REPO_AUTHOR_LC }}/${{ env.IMAGE_NAME }}
tags: |
# latest yes
# tag latest = yes ( no dev )
type=raw,value=latest,enable=${{ !inputs.DEV_RELEASE }}
# tag add amd64
# type=raw,enable=true,priority=1000,value=amd64
# tag add pr tag ( PR or push only )
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=,event=tag
# dispatch add x1.x.x-amd64
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=300,prefix=,suffix=-amd64,value=${{ env.IMAGE_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 }}
# dispatch add amd64-development
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-amd64,value=development
# 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 }}
# tag add tag-arm64
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-amd64,event=tag
# 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 }}
# add development tag to default architecture (amd64)
# 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
# tag add development ( amd64 + only dev )
type=raw,enable=${{ inputs.DEV_RELEASE }},priority=400,prefix=,suffix=,value=development
flavor: |
latest=${{ !inputs.DEV_RELEASE }}
latest=false
labels: |
org.opencontainers.image.VERSION=${{ env.IMAGE_VERSION }}
org.opencontainers.image.BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.vendor=${{ env.IMAGE_DOCKERHUB_AUTHOR }}
org.opencontainers.image.ref.name=${{ env.ref_name }}
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 }}"
annotations: |
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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 }}"
# #
# Release Dockerhub Build and Push Amd64
# #
- name: '📦 Build & Push (linux/amd64)'
id: task_release_dh_push
id: task_release_dh_push_amd64
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
@@ -633,9 +445,112 @@ jobs:
file: Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.task_release_dh_meta.outputs.tags }}
labels: ${{ steps.task_release_dh_meta.outputs.labels }}
tags: |
${{ steps.task_release_dh_meta.outputs.tags }}
provenance: false
sbom: false
build-args: |-
ARCH=amd64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
# #
# Release Dockerhub Export Digest Amd64
# #
- name: '📄 Export Digest (linux/amd64)'
id: task_release_dh_digest_export_amd64
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
run: |
mkdir -p /tmp/build-digest-amd64
digest="${{ steps.task_release_dh_push_amd64.outputs.digest }}"
digest="${digest#sha256:}"
touch "/tmp/build-digest-amd64/$digest"
shell: bash
# #
# Release Dockerhub Upload Digest Amd64
# #
- name: '🔼 Upload Digest (linux/amd64)'
id: task_release_dh_digest_upload_amd64
uses: actions/upload-artifact@v4
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
name: digest-amd64
path: /tmp/build-digest-amd64/*
if-no-files-found: error
retention-days: 10
# #
# Release Dockerhub Build and Push Arm64
# #
- name: '📦 Build & Push (linux/arm64)'
id: task_release_dh_push_arm64
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
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
build-args: |-
ARCH=arm64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
# #
# Release Dockerhub Export Digest Arm64
# #
- name: '📄 Export Digest (linux/arm64)'
id: task_release_dh_digest_export_arm64
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
run: |
mkdir -p /tmp/build-digest-arm64
digest="${{ steps.task_release_dh_push_arm64.outputs.digest }}"
digest="${digest#sha256:}"
touch "/tmp/build-digest-arm64/$digest"
shell: bash
# #
# Release Dockerhub Upload Digest Arm64
# #
- name: '🔼 Upload Digest (linux/arm64)'
id: task_release_dh_digest_upload_arm64
uses: actions/upload-artifact@v4
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
name: digest-arm64
path: /tmp/build-digest-arm64/*
if-no-files-found: error
retention-days: 10
# #
# Release Dockerhub Push Manifest
# #
- name: '📦 Push Manifest'
id: task_release_dh_manifest
uses: int128/docker-manifest-create-action@v2
with:
tags: |
${{ steps.task_release_dh_meta.outputs.tags }}
sources: |
${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_dh_push_amd64.outputs.digest }}
${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_dh_push_arm64.outputs.digest }}
index-annotations: |
${{ steps.task_release_dh_meta.outputs.labels }}
# #
# Release Dockerhub Checkpoint Amd64
@@ -644,19 +559,53 @@ jobs:
- name: '⚠️ Checkpoint'
id: task_release_dh_checkpoint
run: |
echo "registry ............. Dockerhub"
echo "github.actor.......... ${{ github.actor }}"
echo "github.ref ........... ${{ github.ref }}"
echo "github.ref_name ...... ${{ github.ref_name }}"
echo "github.event_name .... ${{ github.event_name }}"
echo "inputs.DRY_RUN ....... ${{ inputs.DRY_RUN }}"
echo "env.AUTHOR ........... ${{ env.IMAGE_DOCKERHUB_AUTHOR }}"
echo "tags ................. ${{ steps.task_release_dh_meta.outputs.tags }}"
echo "labels ............... ${{ steps.task_release_dh_meta.outputs.labels }}"
echo "docker image ......... ${{ env.DOCKER_IMAGE }}"
echo "docker sha ........... ${{ env.DOCKER_SHA }}"
echo "docker image id ...... ${{ steps.task_release_dh_push.outputs.imageid }}"
echo "docker digest ........ ${{ steps.task_release_dh_push.outputs.digest }}"
echo ""
echo "[ GITHUB ] ---------------------------------------------------------------------------------------------"
echo "github.actor.............................. ${{ github.actor }}"
echo "github.ref ............................... ${{ github.ref }}"
echo "github.ref_name .......................... ${{ github.ref_name }}"
echo "github.event_name ........................ ${{ github.event_name }}"
echo "github.repository_owner .................. ${{ github.repository_owner }}"
echo "github.repository ........................ ${{ github.repository }}"
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 ""
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.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_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 ""
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 ""
echo "(release) tags ........................... ${{ steps.task_release_dh_meta.outputs.tags }}"
echo "(release) labels ......................... ${{ steps.task_release_dh_meta.outputs.labels }}"
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 }}"
# #
# Release Dockerhub Get Weekly Commits
@@ -674,7 +623,7 @@ jobs:
# #
- name: '🔔 Send Discord Webhook Message'
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
@@ -684,19 +633,20 @@ jobs:
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-description: |
## 📦 Docker 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.IMAGE_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://github.com/${{ github.repository }}/pkgs/container/${{ env.IMAGE_NAME }}
- https://hub.docker.com/r/${{ env.IMAGE_DOCKERHUB_USERNAME }}/${{ env.IMAGE_NAME }}
- Version: `${{ env.IMAGE_VERSION }}`
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull ${{ env.DOCKER_IMAGE }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}-amd64`
- Pull: `docker pull ${{ env.DOCKER_IMAGE }}@${{ steps.task_release_dh_push.outputs.digest }}`
- Pull: `docker pull ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_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' || '' }}`
- Version: `${{ env.IMAGE_VERSION }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`

View File

@@ -4,10 +4,31 @@
# @url https://github.com/Aetherinox
# @usage deploys docker container to Gitea and send message to discord
# upload this workflow to both the `main` branch of the tvapp2 repository
# @secrets secrets.ADMINSERV_GPG_KEY_ASC gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.ADMINSERV_GPG_PASSPHRASE gpg private key passphrase
# secrets.ORG_BINARYNINJA_GITEA_TOKEN gitea personal access token (classic) with package:write permission
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_RELEASES Discord webhook to report releases from gitea to discord
#
# @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.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
#
# @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/deploy-docker-gitea.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/deploy-docker-gitea.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "📦 Deploy Docker Gitea"
@@ -78,6 +99,18 @@ on:
default: 'aetherinox'
type: string
# #
# Gitea Website
#
# this is the gitea website / url to push to
# #
IMAGE_GITEA_WEBSITE:
description: '🌎 Gitea Website'
required: true
default: 'git.binaryninja.net'
type: string
# #
# true no changes to the repo will be made
# false workflow will behave normally, and push any changes detected to the files
@@ -117,8 +150,12 @@ env:
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
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' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# Jobs
@@ -202,282 +239,21 @@ jobs:
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
# #
# Job Docker Release Gitea Arm64
# Job Docker Release Gitea
# #
job-docker-release-gitea-arm64:
job-docker-release-gitea:
name: >-
📦 Release Gitea Arm64
📦 Release Gitea
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 10
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create ]
permissions:
contents: write
packages: write
attestations: write
id-token: write
steps:
# #
# Release Gitea Start Arm64
# #
- name: '🏳️ Start'
id: task_release_gi_start
run: |
echo "Starting Gitea Docker arm64"
# #
# Release Gitea Checkout Arm64
# #
- name: '✅ Checkout'
id: task_release_gh_checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Release Gitea Get Timestamp
# #
- name: '🕛 Get Timestamp'
id: task_release_set_timestamp
run: |
echo "DOCKER_IMAGE=`echo ${{github.repository}} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV}
echo "DOCKER_SHA=${GITHUB_SHA}" >> $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 Gitea Install Dependencies
# #
- name: '📦 Install Dependencies'
id: task_release_gi_dependencies
run:
sudo apt-get install -qq dos2unix
# #
# Release Gitea Execute dos2unix
# #
- 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 --
echo "✅✅✅ Completed DOS2UNIX ✅✅✅"
# #
# Release Gitea Fix Permissions
# #
- 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)
if [ -n "${WRONG_PERM}" ]; then
echo "⚠️⚠️⚠️ Permissions are invalid ⚠️⚠️⚠️"
for i in ${WRONG_PERM}; do
echo "::error file=${i},line=1,title=Missing Executable Bit::This file needs to be set as executable!"
done
exit 1
else
echo "✅✅✅ Executable permissions are OK ✅✅✅"
fi
# #
# Release Gitea QEMU Arm64
# #
- name: '⚙️ Set up QEMU'
id: task_release_gi_qemu
uses: docker/setup-qemu-action@v3
# #
# Release Gitea Setup BuildX Arm64
# #
- name: '⚙️ Setup Buildx'
id: task_release_gi_buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
driver-opts: 'image=moby/buildkit:latest'
# #
# Release Gitea Registry Login Arm64
# #
- name: '⚙️ Login to Gitea'
id: task_release_gi_registry
uses: docker/login-action@v3
with:
registry: git.binaryninja.net
username: ${{ env.IMAGE_GITEA_USERNAME }}
password: ${{ secrets.ORG_BINARYNINJA_GITEA_TOKEN }}
# #
# Release Gitea Meta Arm64
# #
- name: '🔨 Gitea: Meta - Arm64'
id: task_release_gi_meta
uses: docker/metadata-action@v5
with:
images: |
git.binaryninja.net/${{ env.IMAGE_GITEA_AUTHOR }}/${{ env.IMAGE_NAME }}
tags: |
# latest no
type=raw,value=latest,enable=false
# tag add arm64
# type=raw,enable=true,priority=1000,value=arm64
# dispatch add x1.x.x-arm64
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=300,prefix=,suffix=-arm64,value=${{ env.IMAGE_VERSION }}
# dispatch add arm64-development
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-arm64,value=development
# tag add tag-arm64
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-arm64,event=tag
flavor: |
latest=false
labels: |
org.opencontainers.image.VERSION=${{ env.IMAGE_VERSION }}
org.opencontainers.image.BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.vendor=${{ env.IMAGE_GITEA_AUTHOR }}
org.opencontainers.image.ref.name=${{ env.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
# #
# Release Gitea Build and Push Arm64
# #
- name: '📦 Build & Push (linux/arm64)'
id: task_release_gi_push
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
context: .
file: Dockerfile.aarch64
platforms: linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.task_release_gi_meta.outputs.tags }}
labels: ${{ steps.task_release_gi_meta.outputs.labels }}
provenance: false
# #
# Release Gitea Checkpoint Arm64
# #
- name: '⚠️ Checkpoint'
id: task_release_gi_checkpoint
run: |
echo "registry ............. Gitea"
echo "github.actor.......... ${{ github.actor }}"
echo "github.ref ........... ${{ github.ref }}"
echo "github.ref_name ...... ${{ github.ref_name }}"
echo "github.event_name .... ${{ github.event_name }}"
echo "inputs.DRY_RUN ....... ${{ inputs.DRY_RUN }}"
echo "env.AUTHOR ........... ${{ env.IMAGE_GITEA_AUTHOR }}"
echo "tags ................. ${{ steps.task_release_gi_meta.outputs.tags }}"
echo "labels ............... ${{ steps.task_release_gi_meta.outputs.labels }}"
echo "docker image ......... ${{ env.DOCKER_IMAGE }}"
echo "docker sha ........... ${{ env.DOCKER_SHA }}"
echo "docker image id ...... ${{ steps.task_release_gi_push.outputs.imageid }}"
echo "docker digest ........ ${{ steps.task_release_gi_push.outputs.digest }}"
# #
# Release Gitea Get Weekly Commits
# #
- 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
echo 'EOF' >> $GITHUB_ENV
# #
# Release Gitea Notify Gitea
# #
- name: '🔔 Send Discord Webhook Message'
id: task_release_notifications_discord_send
uses: tsickert/discord-webhook@v6.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
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-description: |
## 📦 Docker Deploy (Gitea) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.IMAGE_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://git.binaryninja.net/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull git.binaryninja.net/${{ env.DOCKER_IMAGE }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}-arm64`
- Pull: `docker pull git.binaryninja.net/${{ env.DOCKER_IMAGE }}@${{ steps.task_release_gi_push.outputs.digest }}`
- Dry Run: `${{ inputs.DRY_RUN }}`
- Source: `Gitea` https://git.binaryninja.net/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
- Version: `${{ env.IMAGE_VERSION }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && '✅ Successful' || '❌ Failed' }}`
### Tags
-# This docker image will use the following tags:
```
${{ steps.task_release_gi_meta.outputs.tags }}
```
### Labels
-# This docker image embeds the following labels:
```
${{ steps.task_release_gi_meta.outputs.labels }}
```
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
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"
# #
# Job Docker Release Gitea Amd64
# #
job-docker-release-gitea-amd64:
name: >-
📦 Release Gitea Amd64
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 10
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create, job-docker-release-gitea-arm64 ]
steps:
# #
@@ -506,12 +282,16 @@ jobs:
- name: '🕛 Get Timestamp'
id: task_release_set_timestamp
run: |
echo "DOCKER_IMAGE=`echo ${{github.repository}} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV}
echo "DOCKER_SHA=${GITHUB_SHA}" >> $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 "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
# #
# Release Gitea Install Dependencies
@@ -529,7 +309,9 @@ 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 --
echo "✅✅✅ Completed DOS2UNIX ✅✅✅"
# #
# Release Gitea Fix Permissions
@@ -577,55 +359,209 @@ jobs:
id: task_release_gi_registry
uses: docker/login-action@v3
with:
registry: git.binaryninja.net
registry: ${{ env.IMAGE_GITEA_WEBSITE }}
username: ${{ env.IMAGE_GITEA_USERNAME }}
password: ${{ secrets.ORG_BINARYNINJA_GITEA_TOKEN }}
# #
# Release Gitea Meta Amd64
# Release Gitea Read Readme
#
# @usage org.opencontainers.image.description=${{ steps.task_release_gi_readme_cache.outputs.content }}
# #
- name: '🔨 Gitea: Meta - Amd64'
id: task_release_gi_meta
- name: '📄 Cache README.md'
id: task_release_gi_readme_cache
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ORG_BINARYNINJA_TOKEN_CL }}
script: |
'use strict'
const { promises: fs } = require('fs')
const main = async () => {
const path = "README.md"
let content = await fs.readFile(path, 'utf8')
core.setOutput('content', content)
}
main().catch(err => core.setFailed(err.message))
# #
# Release Gitea Meta amd64
#
# this version of meta needs one entry for amd64 and one for arm64 because each
# image needs to be uploaded manually, and then merged into a single platform.
# #
- name: '🔨 Gitea: Meta amd64'
id: task_release_gi_meta_amd64
uses: docker/metadata-action@v5
with:
images: |
git.binaryninja.net/${{ env.IMAGE_GITEA_AUTHOR }}/${{ env.IMAGE_NAME }}
${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_AUTHOR_LC }}/${{ env.IMAGE_NAME }}
tags: |
# latest yes
# tag latest = yes ( no dev )
type=raw,value=latest,enable=${{ !inputs.DEV_RELEASE }}
# tag add amd64
# type=raw,enable=true,priority=1000,value=amd64
# dispatch add x1.x.x-amd64
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=300,prefix=,suffix=-amd64,value=${{ env.IMAGE_VERSION }}
# dispatch add amd64-development
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-amd64,value=development
# tag add tag-amd64
# tag add tag-amd64 ( PR or push only )
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-amd64,event=tag
# add development tag to default architecture (amd64)
# 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 }}
# 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 }}
# 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 }}
# 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 }}
# 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
# tag add development ( amd64 + only dev )
type=raw,enable=${{ inputs.DEV_RELEASE }},priority=400,prefix=,suffix=,value=development
flavor: |
latest=${{ !inputs.DEV_RELEASE }}
latest=false
labels: |
org.opencontainers.image.VERSION=${{ env.IMAGE_VERSION }}
org.opencontainers.image.BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.vendor=${{ env.IMAGE_GITEA_AUTHOR }}
org.opencontainers.image.ref.name=${{ env.ref_name }}
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.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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 }}"
# #
# Release Gitea Meta arm64
#
# this version of meta needs one entry for amd64 and one for arm64 because each
# image needs to be uploaded manually, and then merged into a single platform.
# #
- name: '🔨 Gitea: Meta arm64'
id: task_release_gi_meta_arm64
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_AUTHOR_LC }}/${{ env.IMAGE_NAME }}
tags: |
# tag latest = no
type=raw,value=latest,enable=false
# tag add tag-arm64
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 }}
# 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.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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 }}"
# #
# Release Gitea Meta Release
#
# this version of meta needs one entry for amd64 and one for arm64 because each
# image needs to be uploaded manually, and then merged into a single platform.
#
# this meta action is for the final image; which has both platforms merged and
# will be the final release.
# #
- name: '🔨 Gitea: Meta Release'
id: task_release_gi_meta_release
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_AUTHOR_LC }}/${{ env.IMAGE_NAME }}
tags: |
# tag latest = yes ( no dev )
type=raw,value=latest,enable=${{ !inputs.DEV_RELEASE }}
# tag add pr tag ( PR or push only )
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 }}
# 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 }}
# 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 }}
# 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
# tag add development ( amd64 + only dev )
type=raw,enable=${{ inputs.DEV_RELEASE }},priority=400,prefix=,suffix=,value=development
flavor: |
latest=false
labels: |
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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 }}"
# #
# Release Gitea Build and Push Amd64
# #
- name: '📦 Build & Push (linux/amd64)'
id: task_release_gi_push
id: task_release_gi_push_amd64
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
@@ -633,30 +569,173 @@ jobs:
file: Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.task_release_gi_meta.outputs.tags }}
labels: ${{ steps.task_release_gi_meta.outputs.labels }}
labels: ${{ steps.task_release_gi_meta_amd64.outputs.labels }}
tags: |
${{ steps.task_release_gi_meta_amd64.outputs.tags }}
provenance: false
sbom: false
build-args: |-
ARCH=amd64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
# #
# Release Gitea Checkpoint Amd64
# Release Gitea Export Digest Amd64
# #
- name: '📄 Export Digest (linux/amd64)'
id: task_release_gi_digest_export_amd64
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
run: |
mkdir -p /tmp/build-digest-amd64
digest="${{ steps.task_release_gi_push_amd64.outputs.digest }}"
digest="${digest#sha256:}"
touch "/tmp/build-digest-amd64/$digest"
shell: bash
# #
# Release Gitea Upload Digest Amd64
# #
- name: '🔼 Upload Digest (linux/amd64)'
id: task_release_gi_digest_upload_amd64
uses: actions/upload-artifact@v4
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
name: digest-amd64
path: /tmp/build-digest-amd64/*
if-no-files-found: error
retention-days: 10
# #
# Release Gitea Build and Push Arm64
# #
- name: '📦 Build & Push (linux/arm64)'
id: task_release_gi_push_arm64
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
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
build-args: |-
ARCH=arm64
RELEASE=${{ inputs.DEV_RELEASE == true && 'development' || 'stable' }}
VERSION=${{ env.IMAGE_VERSION }}
BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
# #
# Release Gitea Export Digest Arm64
# #
- name: '📄 Export Digest (linux/arm64)'
id: task_release_gi_digest_export_arm64
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
run: |
mkdir -p /tmp/build-digest-arm64
digest="${{ steps.task_release_gi_push_arm64.outputs.digest }}"
digest="${digest#sha256:}"
touch "/tmp/build-digest-arm64/$digest"
shell: bash
# #
# Release Gitea Upload Digest Arm64
# #
- name: '🔼 Upload Digest (linux/arm64)'
id: task_release_gi_digest_upload_arm64
uses: actions/upload-artifact@v4
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
name: digest-arm64
path: /tmp/build-digest-arm64/*
if-no-files-found: error
retention-days: 10
# #
# Release Gitea Push Manifest
# #
- name: '📦 Push Manifest'
id: task_release_gi_manifest
uses: int128/docker-manifest-create-action@v2
with:
tags: |
${{ steps.task_release_gi_meta_release.outputs.tags }}
sources: |
${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gi_push_amd64.outputs.digest }}
${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gi_push_arm64.outputs.digest }}
index-annotations: |
${{ steps.task_release_gi_meta_release.outputs.labels }}
# #
# Release Gitea Checkpoint
# #
- name: '⚠️ Checkpoint'
id: task_release_gi_checkpoint
run: |
echo "registry ............. Gitea"
echo "github.actor.......... ${{ github.actor }}"
echo "github.ref ........... ${{ github.ref }}"
echo "github.ref_name ...... ${{ github.ref_name }}"
echo "github.event_name .... ${{ github.event_name }}"
echo "inputs.DRY_RUN ....... ${{ inputs.DRY_RUN }}"
echo "env.AUTHOR ........... ${{ env.IMAGE_GITEA_AUTHOR }}"
echo "tags ................. ${{ steps.task_release_gi_meta.outputs.tags }}"
echo "labels ............... ${{ steps.task_release_gi_meta.outputs.labels }}"
echo "docker image ......... ${{ env.DOCKER_IMAGE }}"
echo "docker sha ........... ${{ env.DOCKER_SHA }}"
echo "docker image id ...... ${{ steps.task_release_gi_push.outputs.imageid }}"
echo "docker digest ........ ${{ steps.task_release_gi_push.outputs.digest }}"
echo ""
echo "[ GITHUB ] ---------------------------------------------------------------------------------------------"
echo "github.actor.............................. ${{ github.actor }}"
echo "github.ref ............................... ${{ github.ref }}"
echo "github.ref_name .......................... ${{ github.ref_name }}"
echo "github.event_name ........................ ${{ github.event_name }}"
echo "github.repository_owner .................. ${{ github.repository_owner }}"
echo "github.repository ........................ ${{ github.repository }}"
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 ""
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.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_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 ""
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 "(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 "(release) tags ........................... ${{ steps.task_release_gi_meta_release.outputs.tags }}"
echo "(release) labels ......................... ${{ steps.task_release_gi_meta_release.outputs.labels }}"
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 }}"
# #
# Release Gitea Get Weekly Commits
@@ -674,7 +753,7 @@ jobs:
# #
- name: '🔔 Send Discord Webhook Message'
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
@@ -684,19 +763,20 @@ jobs:
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-description: |
## 📦 Docker 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.IMAGE_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://git.binaryninja.net/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
- https://${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull git.binaryninja.net/${{ env.DOCKER_IMAGE }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}-amd64`
- Pull: `docker pull git.binaryninja.net/${{ env.DOCKER_IMAGE }}@${{ steps.task_release_gi_push.outputs.digest }}`
- Dry Run: `${{ inputs.DRY_RUN }}`
- Source: `Gitea` https://git.binaryninja.net/${{ env.IMAGE_GITEA_USERNAME }}/${{ env.IMAGE_NAME }}/packages
- Docker Image: `${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}${{ inputs.DEV_RELEASE == true && '-development' || '' }}`
- Version: `${{ env.IMAGE_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 (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' || '' }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
@@ -707,14 +787,14 @@ jobs:
-# This docker image will use the following tags:
```
${{ steps.task_release_gi_meta.outputs.tags }}
${{ steps.task_release_gi_meta_amd64.outputs.tags }}
```
### Labels
-# This docker image embeds the following labels:
```
${{ steps.task_release_gi_meta.outputs.labels }}
${{ steps.task_release_gi_meta_amd64.outputs.labels }}
```
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"

View File

@@ -0,0 +1,783 @@
# #
# @type github workflow
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage deploys docker container to Gitea.com and send message to discord
# upload this workflow to both the `main` branch of the tvapp2 repository
#
# @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.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
#
# @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/deploy-docker-giteacom.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/deploy-docker-giteacom.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "📦 Deploy Docker Gitea.com"
run-name: "📦 Deploy Docker Gitea.com"
# #
# Triggers
# #
on:
# #
# Trigger Workflow Dispatch
#
# If any values are not provided, will use fallback env variable
# #
workflow_dispatch:
inputs:
# #
# Image Name
#
# used in github image path
# gitea.com/${{ env.IMAGE_GITEA_AUTHOR }}/${{ env.IMAGE_NAME }}
# #
IMAGE_NAME:
description: '📦 Image Name'
required: true
default: 'tvapp2'
type: string
# #
# Image Version
#
# used to create new release tag, and add version to docker image name
# #
IMAGE_VERSION:
description: '🏷️ Image Version'
required: true
default: '1.0.0'
type: string
# #
# Gitea Author
#
# used in github image path
# gitea.com/${{ env.IMAGE_GITEA_AUTHOR }}/${{ env.IMAGE_NAME }}
# #
IMAGE_GITEA_AUTHOR:
description: '🪪 Image Author'
required: true
default: 'BinaryNinja'
type: string
# #
# Gitea Username
#
# this is the user to sign into gitea as.
# #
IMAGE_GITEA_USERNAME:
description: '🪪 Gitea Username'
required: true
default: 'aetherinox'
type: string
# #
# Gitea Website
#
# this is the gitea website / url to push to
# #
IMAGE_GITEA_WEBSITE:
description: '🌎 Gitea Website'
required: true
default: 'gitea.com'
type: string
# #
# true no changes to the repo will be made
# false workflow will behave normally, and push any changes detected to the files
# #
DRY_RUN:
description: '🐛 Dry Run (Debug)'
required: true
default: false
type: boolean
# #
# true released version will be marked as a development build and will have the v1.x.x-development tag instead of -latest
# false release version will be marked with -latest docker tag
# #
DEV_RELEASE:
description: '🧪 Development Release'
required: true
default: false
type: boolean
# #
# Trigger Push
# #
push:
tags:
- '*'
# #
# Environment Vars
# #
env:
IMAGE_NAME: ${{ github.event.inputs.IMAGE_NAME || 'tvapp2' }}
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
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 || 'gitea.com' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# Jobs
#
# The way pushed docker containers on Gitea work, the most recent image built goes at the top.
# We will use the order below which builds the :latest image last so that it appears at the very
# top of the packages page.
# #
jobs:
# #
# Job Create Tag
# #
job-docker-release-tags-create:
name: >-
📦 Release Create Tag
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 4
permissions:
contents: write
packages: write
attestations: write
id-token: write
steps:
# #
# Release Tags Start
# #
- name: '🏳️ Start'
id: task_release_tags_start
run: |
echo "Generating Docker Image on ${{ env.IMAGE_GITEA_WEBSITE }}"
echo "Using original Gitea workflow without manifest"
# #
# Release Tags Checkout
# #
- name: '✅ Checkout'
id: task_release_tags_checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# 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)
if [ -n "${WRONG_PERM}" ]; then
echo "⚠️⚠️⚠️ Permissions are invalid ⚠️⚠️⚠️"
for i in ${WRONG_PERM}; do
echo "::error file=${i},line=1,title=Missing Executable Bit::This file needs to be set as executable!"
done
exit 1
else
echo "✅✅✅ Executable permissions are OK ✅✅✅"
fi
# #
# Release 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_exists_error: false
message: '${{ env.IMAGE_NAME }}-${{ env.IMAGE_VERSION }}'
gpg_private_key: ${{ secrets.ADMINSERV_GPG_KEY_ASC }}
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
# #
# Job Docker Release Gitea Arm64
# #
job-docker-release-gitea-arm64:
name: >-
📦 Release Gitea Arm64
# 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
steps:
# #
# Release Gitea Start Arm64
# #
- name: '🏳️ Start'
id: task_release_gi_start
run: |
echo "Generating Docker Image on ${{ env.IMAGE_GITEA_WEBSITE }} arm64"
echo "Using original Gitea workflow without manifest"
# #
# Release Gitea Checkout Arm64
# #
- name: '✅ Checkout'
id: task_release_gh_checkout
uses: actions/checkout@v4
# #
# Release Gitea Get Timestamp
# #
- name: '🕛 Get Timestamp'
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} # binaryninja/tvapp2
echo "REGISTRY_REPO_AUTHOR_LC=`echo ${{ env.IMAGE_GITEA_AUTHOR }} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} # binaryninja
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
# #
# Release Gitea Install Dependencies
# #
- name: '📦 Install Dependencies'
id: task_release_gi_dependencies
run:
sudo apt-get install -qq dos2unix
# #
# Release Gitea Execute dos2unix
# #
- 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 --
echo "✅✅✅ Completed DOS2UNIX ✅✅✅"
# #
# Release Gitea Fix Permissions
# #
- 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)
if [ -n "${WRONG_PERM}" ]; then
echo "⚠️⚠️⚠️ Permissions are invalid ⚠️⚠️⚠️"
for i in ${WRONG_PERM}; do
echo "::error file=${i},line=1,title=Missing Executable Bit::This file needs to be set as executable!"
done
exit 1
else
echo "✅✅✅ Executable permissions are OK ✅✅✅"
fi
# #
# Release Gitea QEMU Arm64
# #
- name: '⚙️ Set up QEMU'
id: task_release_gi_qemu
uses: docker/setup-qemu-action@v3
# #
# Release Gitea Setup BuildX Arm64
# #
- name: '⚙️ Setup Buildx'
id: task_release_gi_buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
driver-opts: 'image=moby/buildkit:latest'
# #
# Release Gitea Registry Login Arm64
# #
- name: '⚙️ Login to Gitea'
id: task_release_gi_registry
uses: docker/login-action@v3
with:
registry: ${{ env.IMAGE_GITEA_WEBSITE }}
username: ${{ env.IMAGE_GITEA_USERNAME }}
password: ${{ secrets.ORG_BINARYNINJA_GITEACOM_TOKEN }}
# #
# Release Gitea Meta Arm64
# #
- name: '🔨 Gitea: Meta - Arm64'
id: task_release_gi_meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_AUTHOR_LC }}/${{ env.IMAGE_NAME }}
tags: |
# latest no
type=raw,value=latest,enable=false
# tag add arm64
# type=raw,enable=true,priority=1000,value=arm64
# dispatch add x1.x.x-arm64
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=300,prefix=,suffix=-arm64,value=${{ env.IMAGE_VERSION }}
# dispatch add arm64-development
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-arm64,value=development
# tag add tag-arm64
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-arm64,event=tag
flavor: |
latest=false
labels: |
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
# #
# Release Gitea Build and Push Arm64
# #
- name: '📦 Build & Push (linux/arm64)'
id: task_release_gi_push
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
context: .
file: Dockerfile.aarch64
platforms: linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.task_release_gi_meta.outputs.tags }}
labels: ${{ steps.task_release_gi_meta.outputs.labels }}
provenance: false
# #
# Release Gitea Checkpoint Arm64
# #
- name: '⚠️ Checkpoint'
id: task_release_gi_checkpoint
run: |
echo "registry ................................. Gitea"
echo "github.actor.............................. ${{ github.actor }}"
echo "github.ref ............................... ${{ github.ref }}"
echo "github.ref_name .......................... ${{ github.ref_name }}"
echo "github.event_name ........................ ${{ github.event_name }}"
echo "github.repository_owner .................. ${{ github.repository_owner }}"
echo "github.repository ........................ ${{ github.repository }}"
echo "inputs.DRY_RUN ........................... ${{ inputs.DRY_RUN }}"
echo "env.AUTHOR ............................... ${{ env.REGISTRY_REPO_AUTHOR_LC }}"
echo "tags ..................................... ${{ steps.task_release_gi_meta.outputs.tags }}"
echo "labels ................................... ${{ steps.task_release_gi_meta.outputs.labels }}"
echo "docker image ............................. ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}"
echo "docker sha ............................... ${{ env.DOCKER_SHA }}"
echo "docker image id .......................... ${{ steps.task_release_gi_push.outputs.imageid }}"
echo "docker digest ............................ ${{ steps.task_release_gi_push.outputs.digest }}"
# #
# Release Gitea Get Weekly Commits
# #
- 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
echo 'EOF' >> $GITHUB_ENV
# #
# Release Gitea Notify Gitea
# #
- name: '🔔 Send Discord Webhook Message'
id: task_release_notifications_discord_send
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
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-description: |
## 📦 Docker Deploy (Gitea) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.IMAGE_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
- 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 }}-arm64`
- Pull: `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gi_push.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' || '' }}`
- Version: `${{ env.IMAGE_VERSION }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && '✅ Successful' || '❌ Failed' }}`
### Tags
-# This docker image will use the following tags:
```
${{ steps.task_release_gi_meta.outputs.tags }}
```
### Labels
-# This docker image embeds the following labels:
```
${{ steps.task_release_gi_meta.outputs.labels }}
```
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
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"
# #
# Job Docker Release Gitea Amd64
# #
job-docker-release-gitea-amd64:
name: >-
📦 Release Gitea Amd64
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 10
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create, job-docker-release-gitea-arm64 ]
steps:
# #
# Release Gitea Start Amd64
# #
- name: '🏳️ Start'
id: task_release_gi_start
run: |
echo "Generating Docker Image on ${{ env.IMAGE_GITEA_WEBSITE }} amd64"
echo "Using original Gitea workflow without manifest"
# #
# Release Gitea Checkout
# #
- name: '✅ Checkout'
id: task_release_gh_checkout
uses: actions/checkout@v4
# #
# Release Gitea Get Timestamp
# #
- name: '🕛 Get Timestamp'
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} # binaryninja/tvapp2
echo "REGISTRY_REPO_AUTHOR_LC=`echo ${{ env.IMAGE_GITEA_AUTHOR }} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV} # binaryninja
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
# #
# Release Gitea Install Dependencies
# #
- name: '📦 Install Dependencies'
id: task_release_gi_dependencies
run:
sudo apt-get install -qq dos2unix
# #
# Release Gitea Execute dos2unix
# #
- name: '🔐 Apply dos2unix'
id: task_release_gi_dos2unix
run: |
find ./ \( -path "./.git" -o -path "./docs" -o -path "./.github" -o -path "*.png" -o -path "*.jpg" \) -prune -o -name '*' -print | xargs dos2unix --
# #
# Release Gitea Fix Permissions
# #
- 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)
if [ -n "${WRONG_PERM}" ]; then
echo "⚠️⚠️⚠️ Permissions are invalid ⚠️⚠️⚠️"
for i in ${WRONG_PERM}; do
echo "::error file=${i},line=1,title=Missing Executable Bit::This file needs to be set as executable!"
done
exit 1
else
echo "✅✅✅ Executable permissions are OK ✅✅✅"
fi
# #
# Release Gitea QEMU Amd64
# #
- name: '⚙️ Set up QEMU'
id: task_release_gi_qemu
uses: docker/setup-qemu-action@v3
# #
# Release Gitea Setup BuildX Amd64
# #
- name: '⚙️ Setup Buildx'
id: task_release_gi_buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
driver-opts: 'image=moby/buildkit:latest'
# #
# Release Gitea Registry Login Amd64
# #
- name: '⚙️ Login to Gitea'
id: task_release_gi_registry
uses: docker/login-action@v3
with:
registry: ${{ env.IMAGE_GITEA_WEBSITE }}
username: ${{ env.IMAGE_GITEA_USERNAME }}
password: ${{ secrets.ORG_BINARYNINJA_GITEACOM_TOKEN }}
# #
# Release Gitea Meta Amd64
# #
- name: '🔨 Gitea: Meta - Amd64'
id: task_release_gi_meta
uses: docker/metadata-action@v5
with:
images: |
${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_AUTHOR_LC }}/${{ env.IMAGE_NAME }}
tags: |
# latest yes
type=raw,value=latest,enable=${{ !inputs.DEV_RELEASE }}
# tag add amd64
# type=raw,enable=true,priority=1000,value=amd64
# dispatch add x1.x.x-amd64
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=300,prefix=,suffix=-amd64,value=${{ env.IMAGE_VERSION }}
# dispatch add amd64-development
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-amd64,value=development
# tag add tag-amd64
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-amd64,event=tag
# add development tag to default architecture (amd64)
type=raw,enable=${{ inputs.DEV_RELEASE }},priority=400,prefix=,suffix=,value=development
flavor: |
latest=false
labels: |
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
annotations: |
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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.tvapp2.image.build-version="Version:- ${{ env.IMAGE_VERSION }} Date:- ${{ env.NOW_DOCKER_LABEL }}"
# #
# Release Gitea Build and Push Amd64
# #
- name: '📦 Build & Push (linux/amd64)'
id: task_release_gi_push
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.task_release_gi_meta.outputs.tags }}
labels: ${{ steps.task_release_gi_meta.outputs.labels }}
provenance: false
# #
# Release Gitea Checkpoint Amd64
# #
- name: '⚠️ Checkpoint'
id: task_release_gi_checkpoint
run: |
echo "registry ................................. Gitea"
echo "github.actor.............................. ${{ github.actor }}"
echo "github.ref ............................... ${{ github.ref }}"
echo "github.ref_name .......................... ${{ github.ref_name }}"
echo "github.event_name ........................ ${{ github.event_name }}"
echo "github.repository_owner .................. ${{ github.repository_owner }}"
echo "github.repository ........................ ${{ github.repository }}"
echo "inputs.DRY_RUN ........................... ${{ inputs.DRY_RUN }}"
echo "env.AUTHOR ............................... ${{ env.REGISTRY_REPO_AUTHOR_LC }}"
echo "tags ..................................... ${{ steps.task_release_gi_meta.outputs.tags }}"
echo "labels ................................... ${{ steps.task_release_gi_meta.outputs.labels }}"
echo "docker image ............................. ${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}"
echo "docker sha ............................... ${{ env.DOCKER_SHA }}"
echo "docker image id .......................... ${{ steps.task_release_gi_push.outputs.imageid }}"
echo "docker digest ............................ ${{ steps.task_release_gi_push.outputs.digest }}"
# #
# Release Gitea Get Weekly Commits
# #
- 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
echo 'EOF' >> $GITHUB_ENV
# #
# Release Gitea Notify Gitea
# #
- name: '🔔 Send Discord Webhook Message'
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
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-description: |
## 📦 Docker Deploy (Gitea) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.IMAGE_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
- 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 }}-amd64`
- Pull: `docker pull ${{ env.IMAGE_GITEA_WEBSITE }}/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gi_push.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' || '' }}`
- Version: `${{ env.IMAGE_VERSION }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && '✅ Successful' || '❌ Failed' }}`
### Tags
-# This docker image will use the following tags:
```
${{ steps.task_release_gi_meta.outputs.tags }}
```
### Labels
-# This docker image embeds the following labels:
```
${{ steps.task_release_gi_meta.outputs.labels }}
```
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
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"

View File

@@ -4,10 +4,31 @@
# @url https://github.com/Aetherinox
# @usage deploys docker container to github and send message to discord
# upload this workflow to both the `main` branch of the tvapp2 repository
# @secrets secrets.ADMINSERV_GPG_KEY_ASC gpg private key (armored) | BEGIN PGP PRIVATE KEY BLOCK
# secrets.ADMINSERV_GPG_PASSPHRASE gpg private key passphrase
# secrets.ORG_BINARYNINJA_TOKEN_CL github personal access token (classic) with package:write permission
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_RELEASES Discord webhook to report releases from github to discord
#
# @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.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
#
# @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/deploy-docker-github.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/deploy-docker-github.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "📦 Deploy Docker Github"
@@ -117,8 +138,11 @@ env:
IMAGE_VERSION: ${{ github.event.inputs.IMAGE_VERSION || '1.0.0' }}
IMAGE_GHCR_AUTHOR: ${{ github.event.inputs.IMAGE_GHCR_AUTHOR || 'BinaryNinja' }}
IMAGE_GHCR_USERNAME: ${{ github.event.inputs.IMAGE_GHCR_USERNAME || 'BinaryNinja' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# Jobs
@@ -202,280 +226,21 @@ jobs:
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
# #
# Job Docker Release Github Arm64
# Job Docker Release Github
# #
job-docker-release-github-arm64:
job-docker-release-github:
name: >-
📦 Release Github Arm64
📦 Release Github
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 10
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create ]
permissions:
contents: write
packages: write
attestations: write
id-token: write
steps:
# #
# Release Github Start Arm64
# #
- name: '🏳️ Start'
id: task_release_gh_start
run: |
echo "Starting Github Docker arm64"
# #
# Release Github Checkout Arm64
# #
- name: '✅ Checkout'
id: task_release_gh_checkout
uses: actions/checkout@v4
# #
# Release Github Get Timestamp
# #
- name: '🕛 Get Timestamp'
id: task_release_set_timestamp
run: |
echo "DOCKER_IMAGE=`echo ${{github.repository}} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV}
echo "DOCKER_SHA=${GITHUB_SHA}" >> $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 Install Dependencies
# #
- name: '📦 Install Dependencies'
id: task_release_gh_dependencies
run:
sudo apt-get install -qq dos2unix
# #
# Release Github Execute dos2unix
# #
- 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 --
echo "✅✅✅ Completed DOS2UNIX ✅✅✅"
# #
# Release Github Fix Permissions
# #
- 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)
if [ -n "${WRONG_PERM}" ]; then
echo "⚠️⚠️⚠️ Permissions are invalid ⚠️⚠️⚠️"
for i in ${WRONG_PERM}; do
echo "::error file=${i},line=1,title=Missing Executable Bit::This file needs to be set as executable!"
done
exit 1
else
echo "✅✅✅ Executable permissions are OK ✅✅✅"
fi
# #
# Release Github QEMU Arm64
# #
- name: '⚙️ Set up QEMU'
id: task_release_gh_qemu
uses: docker/setup-qemu-action@v3
# #
# Release Github Setup BuildX Arm64
# #
- name: '⚙️ Setup Buildx'
id: task_release_gh_buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
driver-opts: 'image=moby/buildkit:latest'
# #
# Release Github Registry Login Arm64
# #
- name: '⚙️ Login to Github'
id: task_release_gh_registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ env.IMAGE_GHCR_USERNAME }}
password: ${{ secrets.ORG_BINARYNINJA_TOKEN_CL }}
# #
# Release Github Meta Arm64
# #
- name: '🔨 Github: Meta - Arm64'
id: task_release_gh_meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ env.IMAGE_GHCR_AUTHOR }}/${{ env.IMAGE_NAME }}
tags: |
# latest no
type=raw,value=latest,enable=false
# tag add arm64
# type=raw,enable=true,priority=1000,value=arm64
# dispatch add x1.x.x-arm64
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=300,prefix=,suffix=-arm64,value=${{ env.IMAGE_VERSION }}
# dispatch add arm64-development
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-arm64,value=development
# tag add tag-arm64
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-arm64,event=tag
flavor: |
latest=false
labels: |
org.opencontainers.image.VERSION=${{ env.IMAGE_VERSION }}
org.opencontainers.image.BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.vendor=${{ env.IMAGE_GHCR_AUTHOR }}
org.opencontainers.image.ref.name=${{ env.ref_name }}
org.opencontainers.image.development=${{ inputs.DEV_RELEASE == true && 'true' || 'false' }}
# #
# Release Github Build and Push Arm64
# #
- name: '📦 Build & Push (linux/arm64)'
id: task_release_gh_push
uses: docker/build-push-action@v6
if: ( github.event_name == 'workflow_dispatch' && inputs.DRY_RUN == false ) || ( github.event_name == 'push' )
with:
context: .
file: Dockerfile.aarch64
platforms: linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.task_release_gh_meta.outputs.tags }}
labels: ${{ steps.task_release_gh_meta.outputs.labels }}
provenance: false
# #
# Release Github Checkpoint Arm64
# #
- name: '⚠️ Checkpoint'
id: task_release_gh_checkpoint
run: |
echo "registry ............. Github"
echo "github.actor.......... ${{ github.actor }}"
echo "github.ref ........... ${{ github.ref }}"
echo "github.ref_name ...... ${{ github.ref_name }}"
echo "github.event_name .... ${{ github.event_name }}"
echo "inputs.DRY_RUN ....... ${{ inputs.DRY_RUN }}"
echo "env.AUTHOR ........... ${{ env.IMAGE_GHCR_AUTHOR }}"
echo "tags ................. ${{ steps.task_release_gh_meta.outputs.tags }}"
echo "labels ............... ${{ steps.task_release_gh_meta.outputs.labels }}"
echo "docker image ......... ${{ env.DOCKER_IMAGE }}"
echo "docker sha ........... ${{ env.DOCKER_SHA }}"
echo "docker image id ...... ${{ steps.task_release_gh_push.outputs.imageid }}"
echo "docker digest ........ ${{ steps.task_release_gh_push.outputs.digest }}"
# #
# Release Github Get Weekly Commits
# #
- 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
echo 'EOF' >> $GITHUB_ENV
# #
# Release Github Notify Github
# #
- name: '🔔 Send Discord Webhook Message'
id: task_release_notifications_discord_send
uses: tsickert/discord-webhook@v6.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
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-description: |
## 📦 Docker Deploy (Github) ${{ job.status == 'success' && '✅' || '❌' }} `${{ env.IMAGE_NAME }}-${{ env.IMAGE_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 }}
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull ghcr.io/${{ env.DOCKER_IMAGE }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}-arm64`
- Pull: `docker pull ghcr.io/${{ env.DOCKER_IMAGE }}@${{ steps.task_release_gh_push.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' || '' }}`
- Version: `${{ env.IMAGE_VERSION }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && '✅ Successful' || '❌ Failed' }}`
### Tags
-# This docker image will use the following tags:
```
${{ steps.task_release_gh_meta.outputs.tags }}
```
### Labels
-# This docker image embeds the following labels:
```
${{ steps.task_release_gh_meta.outputs.labels }}
```
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
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"
# #
# Job Docker Release Github Amd64
# #
job-docker-release-github-amd64:
name: >-
📦 Release Github Amd64
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 10
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create, job-docker-release-github-arm64 ]
steps:
# #
@@ -502,12 +267,16 @@ jobs:
- name: '🕛 Get Timestamp'
id: task_release_set_timestamp
run: |
echo "DOCKER_IMAGE=`echo ${{github.repository}} | tr '[:upper:]' '[:lower:]'`" >> ${GITHUB_ENV}
echo "DOCKER_SHA=${GITHUB_SHA}" >> $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 "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
# #
# Release Github Install Dependencies
@@ -525,7 +294,9 @@ 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 --
echo "✅✅✅ Completed DOS2UNIX ✅✅✅"
# #
# Release Github Fix Permissions
@@ -578,81 +349,273 @@ jobs:
password: ${{ secrets.ORG_BINARYNINJA_TOKEN_CL }}
# #
# Release Github Meta Amd64
# Release Github Read Readme
#
# @usage org.opencontainers.image.description=${{ steps.task_release_gh_readme_cache.outputs.content }}
# #
- name: '🔨 Github: Meta - Amd64'
- name: '📄 Cache README.md'
id: task_release_gh_readme_cache
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ORG_BINARYNINJA_TOKEN_CL }}
script: |
'use strict'
const { promises: fs } = require('fs')
const main = async () => {
const path = "README.md"
let content = await fs.readFile(path, 'utf8')
core.setOutput('content', content)
}
main().catch(err => core.setFailed(err.message))
# #
# Release Github Meta
#
# this version of meta does not need one for amd64 and one for arm64 because both
# platforms are combined into one release, all sharing the same tags
# #
- name: '🔨 Github: Meta'
id: task_release_gh_meta
uses: docker/metadata-action@v5
with:
images: |
ghcr.io/${{ env.IMAGE_GHCR_AUTHOR }}/${{ env.IMAGE_NAME }}
ghcr.io/${{ env.REGISTRY_REPO_AUTHOR_LC }}/${{ env.IMAGE_NAME }}
tags: |
# latest yes
# tag latest = yes ( no dev )
type=raw,value=latest,enable=${{ !inputs.DEV_RELEASE }}
# tag add amd64
# type=raw,enable=true,priority=1000,value=amd64
# tag add pr tag ( PR or push only )
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=,event=tag
# dispatch add x1.x.x-amd64
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == false }},priority=300,prefix=,suffix=-amd64,value=${{ env.IMAGE_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 }}
# dispatch add amd64-development
type=raw,enable=${{ github.event_name == 'workflow_dispatch' && inputs.DEV_RELEASE == true }},priority=300,prefix=,suffix=-amd64,value=development
# 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 }}
# tag add tag-amd64
type=ref,enable=${{ github.event_name == 'pull_request' || github.event_name == 'push' }},priority=600,prefix=,suffix=-amd64,event=tag
# 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 }}
# add development tag to default architecture (amd64)
# 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
# tag add development ( amd64 + only dev )
type=raw,enable=${{ inputs.DEV_RELEASE }},priority=400,prefix=,suffix=,value=development
flavor: |
latest=${{ !inputs.DEV_RELEASE }}
latest=false
labels: |
org.opencontainers.image.VERSION=${{ env.IMAGE_VERSION }}
org.opencontainers.image.BUILDDATE=${{ env.NOW_DOCKER_LABEL }}
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.vendor=${{ env.IMAGE_GHCR_AUTHOR }}
org.opencontainers.image.ref.name=${{ env.ref_name }}
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 }}"
annotations: |
org.opencontainers.image.created=${{ env.NOW_DOCKER_TS }}
org.opencontainers.image.version=${{ env.IMAGE_VERSION }}
org.opencontainers.image.licenses=MIT
org.opencontainers.image.revision=${{ github.sha }}
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 }}"
# #
# Release Github Build and Push Amd64
# #
- name: '📦 Build & Push (linux/amd64)'
id: task_release_gh_push
id: task_release_gh_push_amd64
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' }}
tags: ${{ steps.task_release_gh_meta.outputs.tags }}
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 }}
# #
# Release Github Checkpoint Amd64
# 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
digest="${{ steps.task_release_gh_push_amd64.outputs.digest }}"
digest="${digest#sha256:}"
touch "/tmp/build-digest-amd64/$digest"
shell: bash
# #
# Release Github Upload Digest Amd64
# #
- 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:
name: digest-amd64
path: /tmp/build-digest-amd64/*
if-no-files-found: error
retention-days: 10
# #
# Release Github Build and Push Arm64
# #
- name: '📦 Build & Push (linux/arm64)'
id: task_release_gh_push_arm64
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_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 }}
# #
# 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
digest="${{ steps.task_release_gh_push_arm64.outputs.digest }}"
digest="${digest#sha256:}"
touch "/tmp/build-digest-arm64/$digest"
shell: bash
# #
# Release Github Upload Digest Arm64
# #
- 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:
name: digest-arm64
path: /tmp/build-digest-arm64/*
if-no-files-found: error
retention-days: 10
# #
# Release Github Push Manifest
# #
- name: '📦 Push Manifest'
id: task_release_gh_manifest
uses: int128/docker-manifest-create-action@v2
with:
push: ${{ !inputs.DRY_RUN }}
tags: |
${{ steps.task_release_gh_meta.outputs.tags }}
sources: |
ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gh_push_amd64.outputs.digest }}
ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}@${{ steps.task_release_gh_push_arm64.outputs.digest }}
index-annotations: |
${{ steps.task_release_gh_meta.outputs.labels }}
# #
# Release Github Checkpoint
# #
- name: '⚠️ Checkpoint'
id: task_release_gh_checkpoint
run: |
echo "registry ............. Github"
echo "github.actor.......... ${{ github.actor }}"
echo "github.ref ........... ${{ github.ref }}"
echo "github.ref_name ...... ${{ github.ref_name }}"
echo "github.event_name .... ${{ github.event_name }}"
echo "inputs.DRY_RUN ....... ${{ inputs.DRY_RUN }}"
echo "env.AUTHOR ........... ${{ env.IMAGE_GHCR_AUTHOR }}"
echo "tags ................. ${{ steps.task_release_gh_meta.outputs.tags }}"
echo "labels ............... ${{ steps.task_release_gh_meta.outputs.labels }}"
echo "docker image ......... ${{ env.DOCKER_IMAGE }}"
echo "docker sha ........... ${{ env.DOCKER_SHA }}"
echo "docker image id ...... ${{ steps.task_release_gh_push.outputs.imageid }}"
echo "docker digest ........ ${{ steps.task_release_gh_push.outputs.digest }}"
echo ""
echo "[ GITHUB ] ---------------------------------------------------------------------------------------------"
echo "github.actor.............................. ${{ github.actor }}"
echo "github.ref ............................... ${{ github.ref }}"
echo "github.ref_name .......................... ${{ github.ref_name }}"
echo "github.event_name ........................ ${{ github.event_name }}"
echo "github.repository_owner .................. ${{ github.repository_owner }}"
echo "github.repository ........................ ${{ github.repository }}"
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 ""
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.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_TS ........................ ${{ env.NOW_DOCKER_TS }}"
echo "env.DOCKER_IMAGE ......................... ${{ env.DOCKER_IMAGE }}"
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 ""
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 ""
echo "(release) tags ........................... ${{ steps.task_release_gh_meta.outputs.tags }}"
echo "(release) labels ......................... ${{ steps.task_release_gh_meta.outputs.labels }}"
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 }}"
# #
# Release Github Get Weekly Commits
@@ -670,7 +633,7 @@ jobs:
# #
- name: '🔔 Send Discord Webhook Message'
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
@@ -680,19 +643,20 @@ jobs:
embed-url: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
embed-thumbnail-url: 'https://i.imgur.com/zDIzE8T.jpg'
embed-description: |
## 📦 Docker 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.IMAGE_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 }}`
- Release Type: `${{ inputs.DEV_RELEASE == true && '⚠️⚠️ Development / Pre-release ⚠️⚠️' || 'Stable' }}`
- Pull: `docker pull ghcr.io/${{ env.DOCKER_IMAGE }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_VERSION }}-amd64`
- Pull: `docker pull ghcr.io/${{ env.DOCKER_IMAGE }}@${{ steps.task_release_gh_push.outputs.digest }}`
- Pull: `docker pull ghcr.io/${{ env.REGISTRY_REPO_ORG_AUTHOR_LC }}:${{ inputs.DEV_RELEASE == true && 'development' || env.IMAGE_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' || '' }}`
- Version: `${{ env.IMAGE_VERSION }}`
- Branch: `${{ github.ref_name }}`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
@@ -718,3 +682,55 @@ jobs:
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"
# #
# Job Docker Release Cleanup
# #
job-docker-release-cleanup:
name: >-
🧹 Release Cleanup
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 5
permissions:
contents: write
packages: write
attestations: write
id-token: write
needs: [ job-docker-release-tags-create, job-docker-release-github ]
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 Clean Untagged Images
# #
- name: '🧹 Clean Untagged Images'
id: task_cleanup_clean
uses: quartx-analytics/ghcr-cleaner@v1
with:
owner-type: org
token: ${{ secrets.ORG_BINARYNINJA_TOKEN_CL }}
repository-owner: ${{ github.repository_owner }}
repository-name: ${{ github.repository }}
delete-untagged: true
# keep-at-most: 0

View File

@@ -1,16 +1,38 @@
# #
# @type github workflow
# @desc builds mkdocs from the main branch /docs/ folder and puts the compiled version
# in the `gh-pages` branch. Is hosted using Github Pages.
#
# @update pip install --upgrade mkdocs
# pip install --upgrade --force-reinstall mkdocs-material
#
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage builds mkdocs from the main branch /docs/ folder and puts the compiled version
# in the `gh-pages` branch. Is hosted using Github Pages.
#
# @secrets secrets.SELF_TOKEN_CL Github Access Token (Classic)
# secrets.DISCORD_WEBHOOK_CHAN_GITHUB_TVAPP2_WORKfLOWS Discord Webbhook URL; right-click on channel, click "Integrations"
# @update use the following commands to update mkdocs and the mkdocs-material theme:
# pip install --upgrade mkdocs
# pip install --upgrade --force-reinstall mkdocs-material
#
# @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.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
#
# @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/documentation.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/documentation.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "📒 Docs Build"
@@ -21,13 +43,55 @@ run-name: "📒 Docs Build"
# #
on:
# #
# Trigger Release
#
# update documentation every time a release is made
# #
release:
types:
- published
# #
# Trigger Push
#
# update documentation every time a file in docs/ folder is modified
# #
push:
paths:
- docs/**
# #
# Trigger Workflow Dispatch
#
# If any values are not provided, will use fallback env variable
# #
workflow_dispatch:
inputs:
# #
# Image Name
#
# used in github image path
# ghcr.io/${{ env.IMAGE_GHCR_AUTHOR }}/${{ env.IMAGE_NAME }}
# #
WORKING_DIR:
description: '📁 Docs Folder'
required: true
default: './docs/site'
type: string
# #
# Trigger Cron
#
# update documentation every X hours
# #
schedule:
- cron: "0 */12 * * *"
@@ -36,9 +100,12 @@ on:
# #
env:
WORKING_DIR: ${{ github.event.inputs.WORKING_DIR || './docs/site' }}
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# jobs
@@ -82,7 +149,8 @@ jobs:
# Documentation Setup Python
# #
- name: "🐍 Setup Python"
- name: >-
🐍 Setup Python
id: task_docs_python_setup
uses: actions/setup-python@v5
with:
@@ -104,6 +172,7 @@ jobs:
pip install mkdocs
pip install mkdocs-material
pip install mike
pip install mkdocs-embed-external-markdown
pip install mkdocs-git-committers-plugin-2
pip install mkdocs-encryptcontent-plugin
pip install mkdocs-redirects mkdocs-glightbox pymdown-extensions mkdocs-git-revision-date-localized-plugin mkdocs-git-authors-plugin mkdocs-link-embeds-plugin
@@ -117,12 +186,13 @@ jobs:
# Documentation Deploy
# #
- name: "💽 Deploy"
- name: >-
💽 Deploy
id: task_docs_deploy
uses: peaceiris/actions-gh-pages@v4
with:
personal_token: ${{ secrets.ADMINSERV_TOKEN_CL }}
publish_dir: ./docs/site
publish_dir: "${{ env.WORKING_DIR }}"
# #
@@ -144,7 +214,7 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_docs_notify_discord_success
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
@@ -178,7 +248,7 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_docs_notify_discord_failure
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: 'Io'

230
.github/workflows/history-clean.yml vendored Normal file
View File

@@ -0,0 +1,230 @@
# #
# @type github workflow
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage cleans all commit history for a repository
# edit the 'environment:' to determine which deployment to keep clean
# - can be ran manually
#
# @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.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_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/history-clean.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/history-clean.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: '🧹 History Clean'
run-name: '🧹 History Clean'
# #
# triggers
# #
on:
# #
# Trigger > Workflow Dispatch
# #
workflow_dispatch:
inputs:
# #
# Commit Label
#
# the label to use when repository is cleaned
# #
COMMIT_LABEL:
description: '🏷️ Commit Label'
required: true
default: 'cleanup'
type: string
# #
# Main Branch
#
# main branch re-recreate
# #
BRANCH_MAIN:
description: '🌳 Main 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' }}
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# jobs
# #
jobs:
history-clean:
name: >-
🧹 History Clean
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 5
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
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# 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]"
# #
# History Clean Pre-Commit
# #
- 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
echo -e "$commit_message"
echo "COMMIT_MESSAGE=$(echo $commit_message)" >> $GITHUB_ENV
echo "NOW=$(echo $now)" >> $GITHUB_ENV
# #
# History Clean Pre-Commit Debug
# #
- name: >-
📦 Commit Pre-commit Debug
id: task_history_clean_commit_debug
run: |
echo -e "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo -e " Printing Values"
echo -e "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――"
echo -e " env.COMMIT_LABEL .................... ${{ env.COMMIT_LABEL }}"
echo -e " env.COMMIT_MESSAGE .................. ${{ env.COMMIT_MESSAGE }}"
echo -e " env.NOW ............................. ${{ env.NOW }}"
# #
# History Clean Start
# #
- name: >-
🧹 Clean Repo History
id: task_history_clean_history
run: |
# Create a new orphan branch
git checkout --orphan temp-branch
# Add all files to the new branch
git add -A
# Commit the files to the new branch
git commit -m "${{ env.COMMIT_MESSAGE }}"
# Delete the old main branch
git branch -D ${{ env.BRANCH_MAIN }}
# Rename the new orphan branch to main
git branch -m ${{ env.BRANCH_MAIN }}
# Force push the new main branch to the remote repository
git push -f origin ${{ env.BRANCH_MAIN }}
# #
# History Clean References
# #
- name: >-
🗑️ Clean References
id: task_history_clean_references
run: |
# Remove remote-tracking references to deleted branches (optional)
git fetch origin --prune
git reflog expire --expire=now --all
git gc --prune=now --aggressive
# #
# History Clean Commit
# #
- name: >-
📦 Commit Execute
id: task_history_clean_commit_execute
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: ${{ env.COMMIT_MESSAGE }}

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,43 @@
# #
# @type github workflow
# @desc pull request autoscan
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage pull request auto-scan
# scans all of the files related to a particular pull request
# if the code in the files being submitted contains code that is forbidden,
# a report is generated and posted as a comment in the PR.
# sends notifications to discord using webhooks
# @author Aetherinox
# @url https://github.com/Aetherinox
#
# @notes skips title changes if the author of the PR is renovate[bat]
#
# @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.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_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/issues-scan.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/issues-scan.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "🎫 Issues Scan"
run-name: "🎫 Issues Scan"
name: '🎫 PR Scan'
run-name: '🎫 PR Scan'
# #
# triggers
@@ -34,12 +61,15 @@ env:
LABEL_CHECK_SECURITY_ERR: AC Security Warning
LABEL_CHECK_STATUS_CHGMADE: AC Changes Made
LABEL_CHECK_SCAN_SKIPPED: AC Skipped Scan
LABEL_TYPE_PR: Type Pull Request
LABEL_TYPE_DEPENDENCY: Type Dependency
LABEL_TYPE_GITACTION: Type Git Action
LABEL_TYPE_PR: Type Pull Request
LABEL_TYPE_DEPENDENCY: Type Dependency
LABEL_TYPE_GITACTION: Type Git Action
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
LABELS_JSON: |
[
@@ -51,32 +81,32 @@ env:
{ "name": "AC Review Required", "color": "8F1784", "description": "PR needs to be reviewed by another person, after the requested changes have been made" },
{ "name": "AC Security Warning", "color": "761620", "description": "Does not conform to developer policies, or includes potentially dangerous code" },
{ "name": "AC Skipped Scan", "color": "8F1784", "description": "Author has skipped code scan" },
{ "name": "Status 𐄂 Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status 𐄂 Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status 𐄂 Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status 𐄂 Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status 𐄂 Locked", "color": "550F45", "description": "Automatically locked by EuropaServ for a prolonged period of inactivity" },
{ "name": "Status 𐄂 Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status 𐄂 No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status 𐄂 Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status 𐄂 Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status 𐄂 Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status 𐄂 Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 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" },
{ "name": "Type Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": "Status Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status Locked", "color": "550F45", "description": "Automatically locked by AdminServ for a prolonged period of inactivity" },
{ "name": "Status Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 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" },
{ "name": "Type Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": " API", "color": "F99B50", "description": "Plugin API, CLI, browser JS API" },
{ "name": " Auto-type", "color": "9141E0", "description": "Auto-type functionality in desktop apps" },
{ "name": " Browser", "color": "9141E0", "description": "Browser plugins and passing data to <=> from app" },
@@ -110,13 +140,17 @@ env:
jobs:
# #
# Job [ Autoscan ]
# Job PR Scan
#
# automatically scan a pull request once it is submitted
# #
pr-autoscan:
job-pr-scan:
name: >-
🎫 Issues Autoscan
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 10
timeout-minutes: 5
permissions:
contents: read
actions: read
@@ -125,27 +159,32 @@ jobs:
steps:
# #
# Cleanup Set Env Variables
# PR Scan Set Env Variables
# #
- name: >-
🕛 Get Timestamp
id: task_autocheck_set_timestamp
id: task_prscan_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
# #
# PR Scan Labels Verify Existing
#
# check if repo has all of the needed issue / pr labels; create label if not exists
#
# action needed if using 'pull_request' and 'issue_comment'
# to get the pull request, you would normally use ${{ github.event.number }}
# however this isnt available for 'issue_comment'
# #
- name: >-
🏷️ Verify Existing Labels
id: task_autocheck_labels_verify
🎫 Labels Verify Existing
id: task_prscan_labels_verify
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
@@ -160,7 +199,7 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
description: label.description || '',
description: label.description || 'No Description',
color: label.color
});
}
@@ -178,13 +217,15 @@ jobs:
}
# #
# set issue number
# PR Scan Assign Pull Request ID to variable
#
# get id (number) for pr when submitted
# #
- name: >-
#️⃣ Issue number Set
#️⃣ Pull-Request ID Set
id: task_prscan_issue_num_set
uses: actions/github-script@v7
id: task_autocheck_issue_num_set
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
script: |
@@ -195,53 +236,61 @@ jobs:
}
else
{
// Otherwise return issue number from commit
return (
const data = (
await github.rest.repos.listPullRequestsAssociatedWithCommit(
{
commit_sha: context.sha,
owner: context.repo.owner,
repo: context.repo.repo,
})
).data[ 0 ].number;
).data[0];
if (data) {
return data.number;
} else {
return '32';
}
}
result-encoding: string
# #
# print issue number
# PR Scan Pull-Request ID Print
#
# prints the pr number detected
# #
- name: >-
#️⃣ Issue number Print
id: task_autocheck_issue_num_get
#️⃣ Pull-Request ID Print
id: task_prscan_issue_num_get
run: |
echo '${{ steps.task_autocheck_issue_num_set.outputs.result }}'
echo '${{ steps.task_prscan_issue_num_set.outputs.result }}'
# #
# checkout
# PR Scan Checkout
# #
- name: >-
☑️ Checkout
id: task_autoscan_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' ) )
with:
token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
fetch-depth: 0
ref: "refs/pull/${{ steps.task_autocheck_issue_num_set.outputs.result }}/merge"
ref: "refs/pull/${{ steps.task_prscan_issue_num_set.outputs.result }}/merge"
# #
# nodejs
# PR Scan Setup NodeJS
# #
- name: >-
⚙️ Setup Node
id: task_autocheck_nodejs
id: task_prscan_nodejs
uses: actions/setup-node@v4
# #
# get list of changed files
# PR Scan Get List of Changed Files
#
# Effortlessly track all changed files and directories relative to a target branch,
# the current branch (preceding commit or the last remote commit), multiple branches,
@@ -251,63 +300,178 @@ jobs:
- name: >-
📄 Get changed files
id: task_autocheck_changed_files_get
uses: tj-actions/changed-files@v45
id: task_prscan_changed_files_get
uses: tj-actions/changed-files@v46
with:
separator: ","
# #
# list of changed files
# PR Scan List All Changed Files
# #
- name: >-
📄 List all added files
id: task_autocheck_added_files_get
id: task_prscan_added_files_get
run: |
for file in ${CHANGED_FILES}; do
echo "$file was changed"
done
env:
ADDED_FILES: ${{ steps.task_autocheck_changed_files_get.outputs.added_files }}
MODIFIED_FILES: ${{ steps.task_autocheck_changed_files_get.outputs.modified_files }}
CHANGED_FILES: ${{ steps.task_autocheck_changed_files_get.outputs.all_changed_files }}
COUNT_ADDED: ${{ steps.task_autocheck_changed_files_get.outputs.added_files_count }}
COUNT_MODIFIED: ${{ steps.task_autocheck_changed_files_get.outputs.modified_files_count }}
COUNT_DELETED: ${{ steps.task_autocheck_changed_files_get.outputs.deleted_files_count }}
COUNT_RENAMED: ${{ steps.task_autocheck_changed_files_get.outputs.renamed_files_count }}
COUNT_COPIED: ${{ steps.task_autocheck_changed_files_get.outputs.copied_files_count }}
ADDED_FILES: ${{ steps.task_prscan_changed_files_get.outputs.added_files }}
MODIFIED_FILES: ${{ steps.task_prscan_changed_files_get.outputs.modified_files }}
CHANGED_FILES: ${{ steps.task_prscan_changed_files_get.outputs.all_changed_files }}
COUNT_ADDED: ${{ steps.task_prscan_changed_files_get.outputs.added_files_count }}
COUNT_MODIFIED: ${{ steps.task_prscan_changed_files_get.outputs.modified_files_count }}
COUNT_DELETED: ${{ steps.task_prscan_changed_files_get.outputs.deleted_files_count }}
COUNT_RENAMED: ${{ steps.task_prscan_changed_files_get.outputs.renamed_files_count }}
COUNT_COPIED: ${{ steps.task_prscan_changed_files_get.outputs.copied_files_count }}
# #
# List directories
# PR Scan List Directories / File Structure
# #
- name: >-
📂 List Directories
id: task_autocheck_dirs_list
id: task_prscan_dirs_list
run: |
ls
# #
# Run autocheck
# PR Scan Autocheck
# #
- name: >-
☑️ Run Autocheck
id: task_autocheck_run
id: task_prscan_run
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
script: |
console.log('Running Autoscan')
console.log(JSON.stringify(context, null, 4));
console.log(JSON.stringify(github, null, 4));
let ct = context;
/* #
# Example PR used for local act testing
# Uncomment to use in local env
#
# can be tested using act:
# - https://github.com/nektos/act
# command:
# git pull https://github.com/username/repo
# act -W .github/workflows/issues-scan.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/issues-scan.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# */
/*
ct = {
"issue": {
"number": 32
},
"repo": {
"owner": "Aetherinox",
"repo": "TheRepoName"
},
"payload": {
"action": "synchronize",
"after": "f087c5bea8800f41018ef328463531ea247547ef",
"before": "a8bdd791b80b2fbb78169234690ccb61b9d014f1",
"number": 32,
"organization": {
"avatar_url": "https://avatars.githubusercontent.com/u/200161462?v=4",
"events_url": "https://api.github.com/orgs/Aetherinox/events",
"hooks_url": "https://api.github.com/orgs/Aetherinox/hooks",
"issues_url": "https://api.github.com/orgs/Aetherinox/issues",
"login": "Aetherinox",
"members_url": "https://api.github.com/orgs/Aetherinox/members{/member}",
"public_members_url": "https://api.github.com/orgs/Aetherinox/public_members{/member}",
"repos_url": "https://api.github.com/orgs/Aetherinox/repos",
"url": "https://api.github.com/orgs/Aetherinox"
},
"pull_request": {
"created_at": "2025-03-17T23:32:22Z",
"updated_at": "2025-03-17T23:32:22Z",
"url": "https://api.github.com/repos/Aetherinox/TheRepoName",
"title": "Test PR Workflow",
"head": {
"ref": "main"
},
"base": {
"ref": "main"
},
"user": {
"login": "Aetherinox"
},
"labels": [
{
"color": "146b4a",
"default": false,
"description": "Ready to be reviewed",
"id": 7821944832,
"name": "AC Passed",
"node_id": "LA_kwDONW-GkM8AAAAB0jloAA",
"url": "https://api.github.com/repos/Aetherinox/TheRepoName/labels/AC%20%E2%80%BA%20Passed"
},
{
"color": "8F1784",
"default": false,
"description": "Normal pull request",
"id": 7821944963,
"name": "Type Pull Request",
"node_id": "LA_kwDONW-GkM8AAAAB0jlogw",
"url": "https://api.github.com/repos/Aetherinox/TheRepoName/labels/Type%20%E2%97%A6%20Pull%20Request"
}
]
},
},
"eventName": "pull_request_target",
"sha": "c938f7a21247f69b29cf352d0c6890a63f260d47",
"ref": "refs/heads/main",
"workflow": "🎫 Issues Scan",
"action": "task_prscan_run",
"actor": "renovate[bot]",
"job": "job-pr-autoscan",
"runNumber": 45,
"runId": 13911964505,
"apiUrl": "https://api.github.com",
"serverUrl": "https://github.com",
"graphqlUrl": "https://api.github.com/graphql"
}
*/
/* #
# if local env ct isn't used, set ct to context for production
# */
if (!ct) {
ct = context;
}
const fs = require( 'fs' );
const escape_html = ( unsafe ) => unsafe.replace( /&/g, '&amp;' ).replace( /</g, '&lt;' ).replace( />/g, '&gt;' ).replace( /"/g, '&quot;' ).replace( /'/g, '&#039;' );
const labels = [];
const files_List = `${{ steps.task_autocheck_changed_files_get.outputs.all_changed_files }}` || ''
/* #
# Get existing labels and add to list
# */
const labelsExisting = await github.rest.issues.listLabelsOnIssue({
issue_number: ct.issue.number,
owner: ct.repo.owner,
repo: ct.repo.repo
})
labelsExisting.data.forEach(({ name }) => {
labels.push(name);
});
const files_List = `${{ steps.task_prscan_changed_files_get.outputs.all_changed_files }}` || ''
const files_Array = files_List.split(',')
const branch_ref = `${ context.payload.pull_request.head.ref }`
const branch_ref = `${ ct.payload.pull_request.head.ref }`
let message = [ "\n<br />\n" ]
message.push ( "## Automatic Self-Check - #" + context.issue.number + "\n" );
message.push ( "## Automatic Self-Check - #" + ct.issue.number + "\n" );
message.push ( `The details of our automated scan for your pull request are listed below. If our scan detected errors, they must be corrected before this pull request will be advanced to the review stage:\n` );
message.push ( "\n<br />\n\n---\n\n<br />\n\n" );
message.push ( "### About\nThis pull request includes the following information:" );
@@ -315,7 +479,7 @@ jobs:
let bHasError = false;
let bHasWarning = false;
let date = new Date( `${ context.payload.pull_request.created_at }` );
let date = new Date( `${ ct.payload.pull_request.created_at }` );
date.toISOString( )
const actor = '${{ github.actor }}';
@@ -341,25 +505,25 @@ jobs:
let date_created = dateTimeformat( date ) + " UTC";
/*
context.payload.pull_request.base.repo.owner.login
*/
/* #
# ct.payload.pull_request.base.repo.owner.login
# */
let md_table =
`
| Category | Value |
| --- | --- |
| Title | [ ` + context.payload.pull_request.title + ` ](https://github.com/` + context.repo.owner + `/` + context.repo.repo + `/pull/` + context.payload.pull_request.number + `) |
| Title | [ ` + ct.payload.pull_request.title + ` ](https://github.com/` + ct.repo.owner + `/` + ct.repo.repo + `/pull/` + ct.payload.pull_request.number + `) |
| Created | [ ` + date_created + ` ](https://worldtimebuddy.com) |
| ID | ` + context.payload.pull_request.html_url + ` |
| Author | [ ` + context.payload.pull_request.user.login + ` ](https://github.com/` + context.repo.owner + `/) |
| Repo | [ ` + context.repo.repo + ` ](https://github.com/` + context.repo.owner + `/` + context.repo.repo + `) |
| Branch | [ ` + context.payload.pull_request.head.ref + `](https://github.com/` + context.repo.owner + `/` + context.repo.repo + `/tree/` + context.payload.pull_request.head.ref + `) ⇁ [ ` + context.payload.pull_request.base.ref + `](https://github.com/` + context.repo.owner + `/` + context.repo.repo + `/tree/` + context.payload.pull_request.base.ref + `) |
| Added Files | ${{ steps.task_autocheck_changed_files_get.outputs.added_files_count }} |
| Modified Files | ${{ steps.task_autocheck_changed_files_get.outputs.all_modified_files_count }} |
| Renamed Files | ${{ steps.task_autocheck_changed_files_get.outputs.renamed_files_count }} |
| Copied Files | ${{ steps.task_autocheck_changed_files_get.outputs.deleted_files_count }} |
| Deleted Files | ${{ steps.task_autocheck_changed_files_get.outputs.deleted_files_count }} |
| ID | ` + ct.payload.pull_request.html_url + ` |
| Author | [ ` + ct.payload.pull_request.user.login + ` ](https://github.com/` + ct.repo.owner + `/) |
| Repo | [ ` + ct.repo.repo + ` ](https://github.com/` + ct.repo.owner + `/` + ct.repo.repo + `) |
| Branch | [ ` + ct.payload.pull_request.head.ref + `](https://github.com/` + ct.repo.owner + `/` + ct.repo.repo + `/tree/` + ct.payload.pull_request.head.ref + `) ⇁ [ ` + ct.payload.pull_request.base.ref + `](https://github.com/` + ct.repo.owner + `/` + ct.repo.repo + `/tree/` + ct.payload.pull_request.base.ref + `) |
| Added Files | ${{ steps.task_prscan_changed_files_get.outputs.added_files_count }} |
| Modified Files | ${{ steps.task_prscan_changed_files_get.outputs.all_modified_files_count }} |
| Renamed Files | ${{ steps.task_prscan_changed_files_get.outputs.renamed_files_count }} |
| Copied Files | ${{ steps.task_prscan_changed_files_get.outputs.deleted_files_count }} |
| Deleted Files | ${{ steps.task_prscan_changed_files_get.outputs.deleted_files_count }} |
`;
message.push ( md_table );
@@ -389,12 +553,14 @@ jobs:
const type_dependency =
[
"dependabot/npm_and_yarn"
"dependabot/npm_and_yarn",
"renovate/github_actions"
];
const type_gitaction =
[
"dependabot/github_actions"
"dependabot/github_actions",
"renovate/github_actions"
];
const files_skipList =
@@ -715,7 +881,7 @@ jobs:
if ( bHasError == true )
{
labels.push( "${{ env.LABEL_CHECK_STATUS_FAILED }}" );
core.setFailed( "Pull Request Failed Autocheck: " + context.issue.number + ": " + context.payload.pull_request.title + "." );
core.setFailed( "Pull Request Failed Autocheck: " + ct.issue.number + ": " + ct.payload.pull_request.title + "." );
}
/*
@@ -729,21 +895,21 @@ jobs:
change pr title
*/
const pr_title = `${ context.payload.pull_request.title }`;
const pr_title_append = `PR ${ context.issue.number }:`;
const pr_title = `${ ct.payload.pull_request.title }`;
const pr_title_append = `PR ${ ct.issue.number }:`;
if ( !pr_title.startsWith( pr_title_append ) )
if ( !pr_title.startsWith( pr_title_append ) && actor != "${{ env.BOT_NAME_RENOVATE }}" )
{
await github.rest.pulls.update(
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
title: `${ pr_title_append } ${ context.payload.pull_request.title }`
owner: ct.repo.owner,
repo: ct.repo.repo,
pull_number: ct.issue.number,
title: `${ pr_title_append } ${ ct.payload.pull_request.title }`
} );
}
if ( !context.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_CHANGES_REQ }}" ).length > 0 )
if ( !ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_CHANGES_REQ }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_REVIEW_READY }}" );
}
@@ -754,24 +920,24 @@ 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 ) );
if ( actor == "${{ env.BOT_NAME_DEPENDABOT }}" && bDependency )
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 )
else if ( actor == "${{ env.BOT_NAME_DEPENDABOT }}" && bGitaction || actor == "${{ env.BOT_NAME_RENOVATE }}" && bGitaction )
labels.push( "${{ env.LABEL_TYPE_GITACTION }}" );
if ( context.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_CHANGES_REQ }}" ).length > 0 )
if ( ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_CHANGES_REQ }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_CHANGES_REQ }}" );
if (context.payload.pull_request.labels.filter(label => label.name === "${{ env.LABEL_CHECK_REBASE_REQ }}" ).length > 0 )
if (ct.payload.pull_request.labels.filter(label => label.name === "${{ env.LABEL_CHECK_REBASE_REQ }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_REBASE_REQ }}" );
if ( context.payload.pull_request.labels.filter(label => label.name === "${{ env.LABEL_CHECK_SECURITY_ERR }}" ).length > 0 )
if ( ct.payload.pull_request.labels.filter(label => label.name === "${{ env.LABEL_CHECK_SECURITY_ERR }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_SECURITY_ERR }}" );
if (context.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_STATUS_CHGMADE }}" ).length > 0 )
if (ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_STATUS_CHGMADE }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_STATUS_CHGMADE }}" );
if ( context.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_SCAN_SKIPPED }}" ).length > 0 )
if ( ct.payload.pull_request.labels.filter( label => label.name === "${{ env.LABEL_CHECK_SCAN_SKIPPED }}" ).length > 0 )
labels.push( "${{ env.LABEL_CHECK_SCAN_SKIPPED }}" );
labels.push( "${{ env.LABEL_TYPE_PR }}" );
@@ -782,9 +948,9 @@ jobs:
await github.rest.issues.setLabels(
{
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ct.issue.number,
owner: ct.repo.owner,
repo: ct.repo.repo,
labels,
} );
@@ -794,9 +960,9 @@ jobs:
await github.rest.issues.createComment(
{
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ct.issue.number,
owner: ct.repo.owner,
repo: ct.repo.repo,
body: message.join('\n'),
} );
@@ -806,12 +972,13 @@ jobs:
- name: >-
🕛 Get Weekly Commit List
id: task_autocheck_set_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
# #
@@ -819,7 +986,7 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_autocheck_notify_discord_success
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
@@ -850,11 +1017,11 @@ jobs:
- Status: `${{ github.event.pull_request.state }}`
### Scan Results
- Added Files: ${{ steps.task_autocheck_changed_files_get.outputs.added_files_count }}
- Modified Files: ${{ steps.task_autocheck_changed_files_get.outputs.all_modified_files_count }}
- Renamed Files: ${{ steps.task_autocheck_changed_files_get.outputs.renamed_files_count }}
- Copied Files: ${{ steps.task_autocheck_changed_files_get.outputs.copied_files_count }}
- Deleted Files: ${{ steps.task_autocheck_changed_files_get.outputs.deleted_files_count }}
- Added Files: ${{ steps.task_prscan_added_files_get.outputs.added_files_count }}
- Modified Files: ${{ steps.task_prscan_added_files_get.outputs.all_modified_files_count }}
- Renamed Files: ${{ steps.task_prscan_added_files_get.outputs.renamed_files_count }}
- Copied Files: ${{ steps.task_prscan_added_files_get.outputs.copied_files_count }}
- Deleted Files: ${{ steps.task_prscan_added_files_get.outputs.deleted_files_count }}
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
@@ -870,7 +1037,7 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_autocheck_notify_discord_failure
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: 'Io'
@@ -901,11 +1068,11 @@ jobs:
- Status: `${{ github.event.pull_request.state }}`
### Scan Results
- Added Files: ${{ steps.task_autocheck_changed_files_get.outputs.added_files_count }}
- Modified Files: ${{ steps.task_autocheck_changed_files_get.outputs.all_modified_files_count }}
- Renamed Files: ${{ steps.task_autocheck_changed_files_get.outputs.renamed_files_count }}
- Copied Files: ${{ steps.task_autocheck_changed_files_get.outputs.copied_files_count }}
- Deleted Files: ${{ steps.task_autocheck_changed_files_get.outputs.deleted_files_count }}
- Added Files: ${{ steps.task_prscan_added_files_get.outputs.added_files_count }}
- Modified Files: ${{ steps.task_prscan_added_files_get.outputs.all_modified_files_count }}
- Renamed Files: ${{ steps.task_prscan_added_files_get.outputs.renamed_files_count }}
- Copied Files: ${{ steps.task_prscan_added_files_get.outputs.copied_files_count }}
- Deleted Files: ${{ steps.task_prscan_added_files_get.outputs.deleted_files_count }}
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"

View File

@@ -1,17 +1,15 @@
# #
# @type github workflow
# @desc creates repository labels if they are not yet installed
# issues marked as stale after 30 days, given tag Status 𐄂 Stale
# inactive issues closed after 180 days, given tag Status 𐄂 Locked
# inactive pr closed after 365 days, given tag Status 𐄂 Locked
# issues marked stale after 30 days, given tag Status 𐄂 Stale
# issues marked closed 7 days after being marked stale, given tag Status 𐄂 Autoclosed
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage creates repository labels if they are not yet installed
# issues marked as stale after 30 days, given tag Status Stale
# inactive issues closed after 180 days, given tag Status Locked
# inactive pr closed after 365 days, given tag Status Locked
# issues marked stale after 30 days, given tag Status Stale
# issues marked closed 7 days after being marked stale, given tag Status Autoclosed
#
# This Github action must be activated manually. This workflow script will do the
# following:
#
# @notes This Github action must be activated manually. This workflow script will do the following:
# - Scan issues / pull requests and make sure they have properly assigned labels:
# - `Bug`
# - `Feature`
@@ -22,10 +20,35 @@
# if they haven't had any replies in 30 days.
#
# - Workflow will `autoclose` pr or issues which haven't had action in `365 days`.
#
# @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.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_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/issues-stale.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/issues-stale.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "🎫 Issues Stale"
run-name: "🎫 Issues Stale"
name: '🎫 Issues Stale'
run-name: '🎫 Issues Stale'
# #
# triggers
@@ -41,28 +64,39 @@ on:
# #
env:
PREFIX_BUG: "Bug"
PREFIX_BUG: "🐛 Bug"
PREFIX_DEPENDENCY: "Dependency"
PREFIX_DOCS: "Docs"
PREFIX_FEATURE: "Feature"
PREFIX_FEATURE: "💡 Feature"
PREFIX_GIT: "Git Action"
PREFIX_PR: "PR"
PREFIX_ROADMAP: "Roadmap"
PREFIX_ROADMAP: "🗺️ Roadmap"
PREFIX_INTERNAL: "Internal"
PREFIX_URGENT: "Urgent"
PREFIX_URGENT: "Urgent"
LABEL_BUG: "Type Bug"
LABEL_DEPENDENCY: "Type Dependency"
LABEL_DOCS: "Type Docs"
LABEL_FEATURE: "Type Feature"
LABEL_GIT: "Type Git Action"
LABEL_PR: "Type Pull Request"
LABEL_ROADMAP: "Type Roadmap"
LABEL_INTERNAL: "Type Internal"
LABEL_BUG: "Type Bug"
LABEL_DEPENDENCY: "Type Dependency"
LABEL_DOCS: "Type Docs"
LABEL_FEATURE: "Type Feature"
LABEL_GIT: "Type Git Action"
LABEL_PR: "Type Pull Request"
LABEL_ROADMAP: "Type Roadmap"
LABEL_INTERNAL: "Type Internal"
LABEL_LOCKED: "Status Locked"
LABEL_STALE: "Status Stale"
LABEL_AUTOCLOSE: "Status Autoclosed"
LABEL_ACCEPTED: "Status Accepted"
LABEL_REVIEW: "Status Review"
LABEL_PENDING: "Status Pending"
LABEL_AC_REVIEW: "AC Review Required"
LABEL_URGENT: "⚠ Urgent"
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
LABELS_JSON: |
[
{ "name": "AC Changes Made", "color": "8F1784", "description": "Requested changes have been made and are pending a re-scan" },
@@ -73,32 +107,32 @@ env:
{ "name": "AC Review Required", "color": "8F1784", "description": "PR needs to be reviewed by another person, after the requested changes have been made" },
{ "name": "AC Security Warning", "color": "761620", "description": "Does not conform to developer policies, or includes potentially dangerous code" },
{ "name": "AC Skipped Scan", "color": "8F1784", "description": "Author has skipped code scan" },
{ "name": "Status 𐄂 Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status 𐄂 Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status 𐄂 Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status 𐄂 Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status 𐄂 Locked", "color": "550F45", "description": "Automatically locked by AdminServ for a prolonged period of inactivity" },
{ "name": "Status 𐄂 Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status 𐄂 No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status 𐄂 Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status 𐄂 Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status 𐄂 Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status 𐄂 Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 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" },
{ "name": "Type Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": "Status Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status Locked", "color": "550F45", "description": "Automatically locked by AdminServ for a prolonged period of inactivity" },
{ "name": "Status Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 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" },
{ "name": "Type Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": " API", "color": "F99B50", "description": "Plugin API, CLI, browser JS API" },
{ "name": " Auto-type", "color": "9141E0", "description": "Auto-type functionality in desktop apps" },
{ "name": " Browser", "color": "9141E0", "description": "Browser plugins and passing data to <=> from app" },
@@ -143,40 +177,60 @@ jobs:
🎫 Labels Verify Existing
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 4
timeout-minutes: 5
steps:
# #
# [ Create Labels ] Start
# Issues (Stale) Labels Create Start
# #
- name: >-
✅ Start
id: task_label_create_start
id: task_labels_create_start
run: |
echo "Assigning labels and assignees"
# #
# [ Create Labels ] Checkout
# Issues (Stale) Labels Create Set Env Variables
# #
- 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
# #
# Issues (Stale) Labels Create Checkout
# #
- name: >-
☑️ Checkout
id: task_label_create_checkout
id: task_labels_create_checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# [ Create Labels ] Verify Existing Labels
# Issues (Stale) Labels Create Verify Existing
#
# check if repo has all of the needed issue / pr labels; create label if not exists
#
# action needed if using 'pull_request' and 'issue_comment'
# to get the pull request, you would normally use ${{ github.event.number }}
# however this isnt available for 'issue_comment'
# #
- name: >-
🏷️ Verify Existing Labels
id: task_label_create_verify
🎫 Labels Verify Existing
id: task_labels_create_verify
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
script: |
const labels = JSON.parse( process.env.LABELS_JSON );
for ( const label of labels )
@@ -188,7 +242,7 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
description: label.description || '',
description: label.description || 'No Description',
color: label.color
});
}
@@ -206,7 +260,7 @@ jobs:
}
# #
# Job [ Check Labels ]
# Issues (Stale) Labels Assign Missing
#
# Runs through all submissions to check for ones that have not been properly labeled
# - Bug
@@ -218,32 +272,35 @@ jobs:
job-issues-nolabel:
name: >-
🎫 Labels Assign Missing
# runs-on: ubuntu-latest
runs-on: apollo-x64
runs-on: ubuntu-latest
timeout-minutes: 4
needs: job-labels-create
steps:
# #
# [ Check Labels ] Checkout
# Issues (Stale) Labels Assign Missing Checkout
# #
- name: "☑️ Prepare"
- name: >-
☑️ Checkout
id: task_issues_nolabel_prepare
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# [ Check Labels ] Check
# Check if repo has labels currently added to issues
# Issues (Stale) Labels Assign Missing Check
#
# Check if repo has labels to use
# #
- name: 🏷️ Checking Issues
- name: >-
🎫 Labels Check
id: task_issues_nolabel_run
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
script: |
/*
@@ -263,7 +320,6 @@ jobs:
hours = hours % 12;
hours = hours ? hours : 12;
minutes = minutes.toString( ).padStart( 2, '0' );
let mergeTime = month + '.' + day + '.' + year + ' ' + hours + ':' + minutes + ' ' + x;
return mergeTime;
@@ -310,8 +366,8 @@ jobs:
let iss_body = `${ issue.body }`;
const iss_body_lc = iss_body.toLowerCase( );
console.log( ` └── 📁 ` + iss_title );
console.log( ` └── 📄 Issue #${ issue.number } last updated on ${ date_UpdateHuman }` );
console.log( ` └── 📁 ` + iss_title + ` #${ issue.number }`);
console.log( ` └── 📄 last updated on ${ date_UpdateHuman }` );
console.log( ` └── 📄 ${add_labels}` );
console.log( `\n\n` )
@@ -319,8 +375,8 @@ jobs:
Keywords
*/
const bug_words = [ "bug", "broke", "issue", "fail" ];
const feat_words = [ "feature", "request", "add support" ];
const bug_words = [ "bug", "broke", "issue", "fail", "wont work" ];
const feat_words = [ "feature", "request", "add", "addition", "enhance", "create" ];
const urgn_words = [ "urgent", "urgency", "emergency", "important", "critical" ];
const road_words = [ "roadmap", "road map", "planned" ];
@@ -339,17 +395,14 @@ jobs:
/*
Label > Bugs
*/
const bug_bIncWordT = bug_words.some( s => s.includes( iss_title_lc ) || iss_title_lc.includes( s ) );
/*
Find regex based phrases
Regex:
https://regex101.com/r/Z99Gnq/2
*/
const bug_bIncWordT = bug_words.some( s => s.includes( iss_title_lc ) || iss_title_lc.includes( s ) );
const bug_findWordList = /^\b(?:I?\s*have\s*(?:a|an)\s*(?:issue|problem|bug))|(?:will\s*not\s*work)|(?:it\s*is\s*(?:broken|broke|stuck))|(?:found\s*(?:an?|the)\s*(?:bug|issue))|(?:can\s*I\s*fix\s*the\s*(?:bug|issue))|(?:(?:does not|doesn'?t|don'?t|won'?t|can'?t|can\s?not|will\s*not)\s*(?:work|load|function))|(?:it\s*(?:will\s?not|won'?t|can\s?not|can'?t))\s*(?:get|find)\s*the\s*(?:website|site|webpage|page)|(?:the\s*(?:window|frame)\s*is\s*(?:blank|white|empty|missing))\b$/igm;
const bug_bFoundMatchTitle = Boolean( bug_findWordList.test( iss_title ) );
const bug_bFoundMatchBody = Boolean( bug_findWordList.test( iss_body ) );
@@ -377,16 +430,49 @@ jobs:
if ( author === `${{ env.BOT_NAME_DEPENDABOT }}` )
core.info( `Skipping: Detected ${ author }` )
// Rename title to contain Bug:
if ( author === `${{ env.BOT_NAME_RENOVATE }}` )
core.info( `Skipping: Detected ${ author }` )
/*
Rename title to contain Bug:
if bug title or body contains keyword hinting at the issue being about a bug; change the title of the issue
@ref https://jsfiddle.net/aetherinox/wj17x8mp/2/
*/
if ( author !== `${{ env.BOT_NAME_DEPENDABOT }}` && !bug_bFoundPRTitle && !iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( road_tag.toLowerCase( ) ) )
{
const title = issue.title;
/*
If a user creates an issue starting with our tag; strip it and add ours with the emoji instead
original: Bug: CMD windows opens then closes, can't run the script.
new: 🐛 Bug: CMD windows opens then closes, can't run the script.
*/
const removeBeginning1 = bug_tag.substring(3); // "Bug:"
let removeBeginning2 = bug_tag.substring(0); // "🐛 Bug:"
removeBeginning2 = removeBeginning2.replace(/\s/g, '') // "Bug:"
if ( iss_title.startsWith(removeBeginning1) )
{
iss_title = iss_title.slice(removeBeginning1.length);
iss_title = iss_title.trim();
}
else if ( iss_title.startsWith(removeBeginning2) )
{
iss_title = iss_title.slice(removeBeginning2.length);
iss_title = iss_title.trim();
}
const title = iss_title;
let title_new = title.replace( /^\s?bug\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?fail\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?issue\s*(.*?)\b/gi, '' );
iss_title = `${ bug_tag } ${ title_new }`;
}
console.log( `New Title: ...................... ${ iss_title }` )
await github.rest.issues.update(
{
owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number,
@@ -407,7 +493,7 @@ jobs:
https://regex101.com/r/fR1Hm6/1
*/
const feat_findWordList = /^(?:(?:request|include|see)\s*(?:an?|the?)\s*(?:feature|addon|addition|plugin))|(?:(?:add|see|get)\s*support\s*(?:for|with|of))|(?:can\s*we\s*get\s*(?:the|a)\s*(?:ability|feature))|(?:💡 Feature:)$/igm;
const feat_findWordList = /(?:(?:add|enjoy|would|like|can|request|include|see|could|have)\s*?(?:the|liked?|i|we|an?|the|you?)\s*?(?:to|have|an|get|ability|request|add|feature|functionality|addon|addition|plugin|create))|(?:(?:add|see|get)\s*?support\s*?(?:for|with|of))|(?:can\s*we\s*get\s*?(?:the|a)\s*?(?:ability|feature))|(?:💡 Feature:)$/igm;
const feat_bFoundMatchTitle = Boolean( feat_findWordList.test( iss_title ) );
const feat_bFoundMatchBody = Boolean( feat_findWordList.test( iss_body ) );
@@ -434,17 +520,49 @@ jobs:
if ( author === `${{ env.BOT_NAME_DEPENDABOT }}` )
core.info( `Skipping: Detected ${ author }` )
// Rename title to contain Feature:
if ( author === `${{ env.BOT_NAME_RENOVATE }}` )
core.info( `Skipping: Detected ${ author }` )
/*
Rename title to contain Feature:
if feature title or body contains keyword hinting at the issue being about a feature; change the title of the issue
@ref https://jsfiddle.net/aetherinox/wj17x8mp/2/
*/
if ( author !== `${{ env.BOT_NAME_DEPENDABOT }}` && !feat_bFoundPRTitle && !iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( road_tag.toLowerCase( ) ) )
{
const title = issue.title;
/*
If a user creates an issue starting with our tag; strip it and add ours with the emoji instead
original: Feature: CMD windows opens then closes, can't run the script.
new: 💡 Feature: CMD windows opens then closes, can't run the script.
*/
const removeBeginning1 = feat_tag.substring(3); // "Feature:"
let removeBeginning2 = feat_tag.substring(0); // "💡 Feature:"
removeBeginning2 = removeBeginning2.replace(/\s/g, '') // "Feature:"
if ( iss_title.startsWith(removeBeginning1) )
{
iss_title = iss_title.slice(removeBeginning1.length);
iss_title = iss_title.trim();
}
else if ( iss_title.startsWith(removeBeginning2) )
{
iss_title = iss_title.slice(removeBeginning2.length);
iss_title = iss_title.trim();
}
const title = iss_title;
let title_new = title.replace( /^\s?feature\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?request\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?add(.*?)\s?feature\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?add(.*?)\s?support\s*(.*?)\b/gi, '' );
iss_title = `${ feat_tag } ${ title_new }`;
iss_title = `${ feat_tag } ${ title_new }`; // change TAG per category
}
console.log( `New Title: ...................... ${ iss_title }` )
await github.rest.issues.update(
{
owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number,
@@ -492,10 +610,40 @@ jobs:
if ( author === `${{ env.BOT_NAME_DEPENDABOT }}` )
core.info( `Skipping: Detected ${ author }` )
// Rename title to contain Urgent:
if ( author === `${{ env.BOT_NAME_RENOVATE }}` )
core.info( `Skipping: Detected ${ author }` )
/*
Rename title to contain Urgent:
if urgent title or body contains keyword hinting at the issue being about urgent; change the title of the issue
@ref https://jsfiddle.net/aetherinox/wj17x8mp/2/
*/
if ( author !== `${{ env.BOT_NAME_DEPENDABOT }}` && !urgn_bFoundPRTitle && !iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( road_tag.toLowerCase( ) ) )
{
const title = issue.title;
/*
If a user creates an issue starting with our tag; strip it and add ours with the emoji instead
original: Urgent: CMD windows opens then closes, can't run the script.
new: ⚠ Urgent: CMD windows opens then closes, can't run the script.
*/
const removeBeginning1 = urgn_tag.substring(3); // "Urgent:"
let removeBeginning2 = urgn_tag.substring(0); // "⚠ Urgent:"
removeBeginning2 = removeBeginning2.replace(/\s/g, '') // "Urgent:"
if ( iss_title.startsWith(removeBeginning1) )
{
iss_title = iss_title.slice(removeBeginning1.length);
iss_title = iss_title.trim();
}
else if ( iss_title.startsWith(removeBeginning2) )
{
iss_title = iss_title.slice(removeBeginning2.length);
iss_title = iss_title.trim();
}
const title = iss_title;
let title_new = title.replace( /^\s?emergency\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?urgent\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?urgency\s*(.*?)\b/gi, '' );
@@ -504,6 +652,8 @@ jobs:
iss_title = `${ urgn_tag } ${ title_new }`;
}
console.log( `New Title: ...................... ${ iss_title }` )
await github.rest.issues.update(
{
owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number,
@@ -552,17 +702,47 @@ jobs:
if ( author === `${{ env.BOT_NAME_DEPENDABOT }}` )
core.info( `Skipping: Detected ${ author }` )
// Rename title to contain Roadmap:
if ( author === `${{ env.BOT_NAME_RENOVATE }}` )
core.info( `Skipping: Detected ${ author }` )
/*
Rename title to contain Roadmap:
if roadmap title or body contains keyword hinting at the issue being about roadmap; change the title of the issue
@ref https://jsfiddle.net/aetherinox/wj17x8mp/2/
*/
if ( author !== `${{ env.BOT_NAME_DEPENDABOT }}` && !road_bFoundPRTitle && !iss_title_lc.startsWith( bug_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( feat_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( urgn_tag.toLowerCase( ) ) && !iss_title_lc.startsWith( road_tag.toLowerCase( ) ) )
{
const title = issue.title;
let title_new = title.replace( /^\s?emergency\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?urgent\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?urgency\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?important\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?critical\s*(.*?)\b/gi, '' );
iss_title = `${ road_tag } ${ title_new }`;
/*
If a user creates an issue starting with our tag; strip it and add ours with the emoji instead
original: Roadmap: CMD windows opens then closes, can't run the script.
new: 🗺️ Roadmap: CMD windows opens then closes, can't run the script.
*/
const removeBeginning1 = road_tag.substring(3); // "Roadmap:"
let removeBeginning2 = road_tag.substring(0); // "🗺️ Roadmap:"
removeBeginning2 = removeBeginning2.replace(/\s/g, '') // "Roadmap:"
if ( iss_title.startsWith(removeBeginning1) )
{
iss_title = iss_title.slice(removeBeginning1.length);
iss_title = iss_title.trim();
}
else if ( iss_title.startsWith(removeBeginning2) )
{
iss_title = iss_title.slice(removeBeginning2.length);
iss_title = iss_title.trim();
}
const title = iss_title;
let title_new = title.replace( /^\s?broad(.*?)\s?map\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?planned\s*(.*?)\b/gi, '' );
title_new = title.replace( /^\s?broadmap\s*(.*?)\b/gi, '' );
iss_title = `${ road_tag } ${ title_new }`; // change TAG per category
}
console.log( `New Title: ...................... ${ iss_title }` )
await github.rest.issues.update(
{
@@ -581,15 +761,15 @@ jobs:
}
# #
# Job [ Stale Issues ]
# Issues (Stale) Stale
# #
job-issues-stale:
name: >-
💤 Check Stale
💤 Scan Check Stale
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 4
timeout-minutes: 5
needs:
- job-labels-create
- job-issues-nolabel
@@ -600,14 +780,15 @@ jobs:
steps:
# #
# [ Stale Issues ] Check Condition
# Stale Check Condition
# #
- name: "💤 Stale Check Condition"
- name: >-
💤 Stale Check Condition
id: task_issues_inactive_stale
uses: actions/stale@v9
id: task_issues_stale_run
with:
repo-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
repo-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
stale-issue-message: |
⚠️ It looks like there hasn't been any recent updates on this issue. If you created this issue and no longer consider it open, then please login to github and close the issue.
@@ -616,41 +797,42 @@ jobs:
---
<sub>I am a bot reaching out to you with an automated response.</sub>
stale-issue-label: 'Status 𐄂 Stale'
close-issue-label: 'Status 𐄂 Autoclosed'
exempt-issue-labels: 'Status 𐄂 Accepted,Status 𐄂 Review,Status 𐄂 Pending,Type ◦ Bug,Type ◦ Dependency,Type ◦ Docs,Type ◦ Feature,Type ◦ Git Action,Type ◦ Pull Request,Type ◦ Roadmap'
days-before-stale: 60
stale-issue-label: '${{ env.LABEL_STALE }}'
close-issue-label: '${{ env.LABEL_AUTOCLOSE }}'
exempt-issue-labels: '${{ env.LABEL_ACCEPTED }},${{ env.LABEL_REVIEW }},${{ env.LABEL_PENDING }},${{ env.LABEL_BUG }},${{ env.LABEL_DEPENDENCY }},${{ env.LABEL_DOCS }},${{ env.LABEL_FEATURE }},${{ env.LABEL_GIT }},${{ env.LABEL_PR }},${{ env.LABEL_ROADMAP }}'
days-before-stale: 120
days-before-close: 7
days-before-pr-stale: -1
days-before-pr-close: -1
# #
# Job [ Lock Issues ]
# Issues (Stale) Lock
# #
job-issues-lock:
name: >-
🔒 Check Inactive
🔒 Scan Lock Inactive
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 4
timeout-minutes: 5
needs:
- job-labels-create
- job-issues-nolabel
steps:
# #
# [ Lock Issues ] Look for inactives
# Inactive Lock
# #
- name: "🔒 Lock Inactives"
- name: >-
🔒 Inactive Lock
id: task_issues_inactive_lock
uses: dessant/lock-threads@v5
id: task_issues_lock_run
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
exclude-any-issue-labels: 'AC Review Required,Status 𐄂 Accepted,Status 𐄂 Review,Status 𐄂 Pending,Type ◦ Bug,Type ◦ Dependency,Type ◦ Docs,Type ◦ Feature,Type ◦ Git Action,Type ◦ Roadmap,Type ◦ Internal'
add-issue-labels: 'Status 𐄂 Locked'
issue-inactive-days: '60'
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
exclude-any-issue-labels: '${{ env.LABEL_AC_REVIEW }},${{ env.LABEL_ACCEPTED }},${{ env.LABEL_REVIEW }},${{ env.LABEL_PENDING }},${{ env.LABEL_BUG }},${{ env.LABEL_DEPENDENCY }},${{ env.LABEL_DOCS }},${{ env.LABEL_FEATURE }},${{ env.LABEL_GIT }},${{ env.LABEL_ROADMAP }},${{ env.LABEL_INTERNAL }}'
add-issue-labels: '${{ env.LABEL_LOCKED }}'
issue-inactive-days: '120'
issue-lock-reason: 'resolved'
issue-comment: >
⚠️ This **issue** has been automatically locked since there has not been any recent activity after it was closed.
@@ -660,8 +842,8 @@ jobs:
---
<sub>I am a bot reaching out to you with an automated response.</sub>
exclude-any-pr-labels: 'AC Review Required,Status 𐄂 Accepted,Status 𐄂 Review,Status 𐄂 Pending,Type ◦ Bug,Type ◦ Dependency,Type ◦ Docs,Type ◦ Feature,Type ◦ Git Action,Type ◦ Roadmap,Type ◦ Internal'
add-pr-labels: 'Status 𐄂 Locked'
exclude-any-pr-labels: '${{ env.LABEL_AC_REVIEW }},${{ env.LABEL_ACCEPTED }},${{ env.LABEL_REVIEW }},${{ env.LABEL_PENDING }},${{ env.LABEL_BUG }},${{ env.LABEL_DEPENDENCY }},${{ env.LABEL_DOCS }},${{ env.LABEL_FEATURE }},${{ env.LABEL_GIT }},${{ env.LABEL_ROADMAP }},${{ env.LABEL_INTERNAL }}'
add-pr-labels: '${{ env.LABEL_LOCKED }}'
pr-inactive-days: '365'
pr-lock-reason: 'resolved'
pr-comment: >

View File

@@ -4,14 +4,37 @@
# @author Aetherinox
# @url https://github.com/Aetherinox
#
# This Github action must be activated manually. This workflow script will do the
# following:
#
# @notes This Github action must be activated manually. This workflow script will do the following:
# - Remove all existing labels in repository
#
# @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.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_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/labels-clean.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/labels-clean.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "🎫 Labels Remove"
run-name: "🎫 Labels Remove"
name: '🧹 Labels Clean'
run-name: '🧹 Labels Clean'
# #
# triggers
@@ -25,8 +48,12 @@ on:
# #
env:
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
LABELS_JSON: |
[
{ "name": "bug", "color": "8F1784", "description": "Default github label" },
@@ -46,32 +73,32 @@ env:
{ "name": "AC Review Required", "color": "8F1784", "description": "PR needs to be reviewed by another person, after the requested changes have been made" },
{ "name": "AC Security Warning", "color": "761620", "description": "Does not conform to developer policies, or includes potentially dangerous code" },
{ "name": "AC Skipped Scan", "color": "8F1784", "description": "Author has skipped code scan" },
{ "name": "Status 𐄂 Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status 𐄂 Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status 𐄂 Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status 𐄂 Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status 𐄂 Locked", "color": "550F45", "description": "Automatically locked by AdminServ for a prolonged period of inactivity" },
{ "name": "Status 𐄂 Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status 𐄂 No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status 𐄂 Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status 𐄂 Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status 𐄂 Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status 𐄂 Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 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" },
{ "name": "Type Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": "Status Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status Locked", "color": "550F45", "description": "Automatically locked by AdminServ for a prolonged period of inactivity" },
{ "name": "Status Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 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" },
{ "name": "Type Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": " API", "color": "F99B50", "description": "Plugin API, CLI, browser JS API" },
{ "name": " Auto-type", "color": "9141E0", "description": "Auto-type functionality in desktop apps" },
{ "name": " Browser", "color": "9141E0", "description": "Browser plugins and passing data to <=> from app" },
@@ -110,12 +137,12 @@ jobs:
# This job removes all existing labels
# #
issues-labels-remove:
issues-labels-clean:
name: >-
🎫 Labels Remove
🧹 Labels Clean
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 4
timeout-minutes: 3
permissions:
contents: 'read'
id-token: 'write'
@@ -140,6 +167,7 @@ jobs:
🕛 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
@@ -165,7 +193,7 @@ jobs:
id: task_label_remove_run
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
script: |
const targetOwner = context.repo.owner;
const targetRepo = context.repo.repo;
@@ -222,73 +250,3 @@ jobs:
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
# #
# Labels Notify Github Success
# #
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_label_notify_discord_success
uses: tsickert/discord-webhook@v6.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_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-description: |
## 🎫 Labels Clean ${{ job.status == 'success' && '✅' || '❌' }}
A **successful** workflow has been ran to wipe all labels from your repository.
**${{ steps.task_label_remove_run.outputs.result }}** labels have been removed.
- Labels: `${{ steps.task_label_remove_run.outputs.result }} created`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && '✅ Successful' || '❌ Failed' }}`
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
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"
# #
# Labels Notify Github Failure
# #
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_label_notify_discord_failure
uses: tsickert/discord-webhook@v6.0.0
if: failure()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_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-description: |
## 🎫 Labels Clean ${{ job.status == 'success' && '✅' || '❌' }}
A **failed** attempt was made to run this workflow. No new labels have been added to your repository.
- Labels: `${{ steps.task_label_remove_run.outputs.result }} created`
- Workflow: `${{ github.workflow }} (#${{github.run_number}})`
- Runner: `${{ runner.name }}`
- Triggered By: `${{ github.actor }}`
- Status: `${{ job.status == 'success' && '✅ Successful' || '❌ Failed' }}`
embed-color: ${{ job.status == 'success' && '5763719' || '15418782' }}
embed-footer-text: "Completed at ${{ env.NOW }} UTC"
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"

View File

@@ -1,26 +1,37 @@
# #
# @type github workflow
# @desc manually activated workflow to create issue labels
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage manually activated workflow to create issue labels
#
# This Github action must be activated manually. This workflow script will do the
# following:
# @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.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_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
#
# - Scan issues / pull requests and make sure they have properly assigned labels:
# - `Bug`
# - `Feature`
# - `Urgent`
# - `Roadmap`
#
# - Workflow script will then scan each pr or issue and mark them as `Stale`
# if they haven't had any replies in 30 days.
#
# - Workflow will `autoclose` pr or issues which haven't had action in `365 days`.
# @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/labels-create.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/labels-create.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "🎫 Labels Create"
run-name: "🎫 Labels Create"
name: '🎫 Labels Create'
run-name: '🎫 Labels Create'
# #
# triggers
@@ -34,8 +45,12 @@ on:
# #
env:
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
LABELS_JSON: |
[
{ "name": "AC Changes Made", "color": "8F1784", "description": "Requested changes have been made and are pending a re-scan" },
@@ -46,32 +61,32 @@ env:
{ "name": "AC Review Required", "color": "8F1784", "description": "PR needs to be reviewed by another person, after the requested changes have been made" },
{ "name": "AC Security Warning", "color": "761620", "description": "Does not conform to developer policies, or includes potentially dangerous code" },
{ "name": "AC Skipped Scan", "color": "8F1784", "description": "Author has skipped code scan" },
{ "name": "Status 𐄂 Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status 𐄂 Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status 𐄂 Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status 𐄂 Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status 𐄂 Locked", "color": "550F45", "description": "Automatically locked by AdminServ for a prolonged period of inactivity" },
{ "name": "Status 𐄂 Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status 𐄂 No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status 𐄂 Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status 𐄂 Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status 𐄂 Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status 𐄂 Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 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" },
{ "name": "Type Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": "Status Duplicate", "color": "75536b", "description": "Issue or pull request already exists" },
{ "name": "Status Accepted", "color": "2e7539", "description": "This pull request has been accepted" },
{ "name": "Status Autoclosed", "color": "3E0915", "description": "Originally stale and was autoclosed for no activity" },
{ "name": "Status Denied", "color": "ba4058", "description": "Pull request has been denied" },
{ "name": "Status Locked", "color": "550F45", "description": "Automatically locked by AdminServ for a prolonged period of inactivity" },
{ "name": "Status Need Info", "color": "2E3C4C", "description": "Not enough information to resolve" },
{ "name": "Status No Action", "color": "030406", "description": "Closed without any action being taken" },
{ "name": "Status Pending", "color": "984b12", "description": "Pending pull request" },
{ "name": "Status Released", "color": "1b6626", "description": "Issues or PR has been implemented and is now live" },
{ "name": "Status Reopened", "color": "8a6f14", "description": "A previously closed PR which has been re-opened" },
{ "name": "Status Review", "color": "9e1451", "description": "Currently pending review" },
{ "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 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" },
{ "name": "Type Pull Request", "color": "8F1784", "description": "Normal pull request" },
{ "name": "Type Roadmap", "color": "8F1784", "description": "Feature or bug currently planned for implementation" },
{ "name": "Type Internal", "color": "A51994", "description": "Assigned items are for internal developer use" },
{ "name": "Build Desktop", "color": "c7ca4a", "description": "Specific to desktop" },
{ "name": "Build Linux", "color": "c7ca4a", "description": "Specific to Linux" },
{ "name": "Build MacOS", "color": "c7ca4a", "description": "Specific to MacOS" },
{ "name": "Build Mobile", "color": "c7ca4a", "description": "Specific to mobile" },
{ "name": "Build Web", "color": "c7ca4a", "description": "Specific to web" },
{ "name": "Build Windows", "color": "c7ca4a", "description": "Specific to Windows" },
{ "name": " API", "color": "F99B50", "description": "Plugin API, CLI, browser JS API" },
{ "name": " Auto-type", "color": "9141E0", "description": "Auto-type functionality in desktop apps" },
{ "name": " Browser", "color": "9141E0", "description": "Browser plugins and passing data to <=> from app" },
@@ -114,7 +129,9 @@ jobs:
issues-labels-create:
name: >-
🎫 Labels Create
runs-on: ubuntu-latest
# runs-on: ubuntu-latest
runs-on: apollo-x64
timeout-minutes: 3
permissions:
contents: 'read'
id-token: 'write'
@@ -139,6 +156,7 @@ jobs:
🕛 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
@@ -164,7 +182,7 @@ jobs:
id: task_label_verify_existing
uses: actions/github-script@v7
with:
github-token: ${{ secrets.ADMINSERV_TOKEN_CL }}
github-token: ${{ secrets.ADMINSERV_TOKEN_CL || github.token }}
script: |
const labels = JSON.parse( process.env.LABELS_JSON );
let result = Object.keys(labels).length;
@@ -217,12 +235,12 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Success)
id: task_label_notify_discord_success
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: success()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_TVAPP2_WORKFLOWS }}
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'
@@ -253,12 +271,12 @@ jobs:
- name: >-
🔔 Send Discord Webhook Message (Failure)
id: task_label_notify_discord_failure
uses: tsickert/discord-webhook@v6.0.0
uses: tsickert/discord-webhook@v7.0.0
if: failure()
with:
username: 'Io'
avatar-url: 'https://i.imgur.com/8BVDkla.jpg'
webhook-url: ${{ secrets.DISCORD_WEBHOOK_CHAN_TVAPP2_WORKFLOWS }}
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'

View File

@@ -1,12 +1,37 @@
# #
# @type github workflow
# @desc publishes a new release on Github
# @author Aetherinox
# @url https://github.com/Aetherinox
# @usage publishes a new release on Github
#
# @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.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
#
# @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/labels-create.yml -P ubuntu-latest=catthehacker/ubuntu:full-22.04
# act -W .github/workflows/labels-create.yml -s TOKEN_CL=XXXXXXXXXX --pull=false
# #
name: "📦 Release Github"
run-name: "📦 Release Github"
name: '📦 Release Github'
run-name: '📦 Release Github'
# #
# Triggers
@@ -22,7 +47,7 @@ on:
# #
PROJECT_NAME:
description: "📦 Name of App"
description: '📦 Name of App'
required: true
default: 'tvapp2'
type: string
@@ -33,7 +58,7 @@ on:
# #
CHANGELOG_MODE_COMMIT:
description: "📑 Use Commits Instead of PRs"
description: '📑 Use Commits Instead of PRs'
required: true
default: true
type: boolean
@@ -46,7 +71,7 @@ on:
# #
SHOW_UNCATEGORIZED:
description: "🗂️ Show Uncategorized Commits"
description: '🗂️ Show Uncategorized Commits'
required: true
default: false
type: boolean
@@ -57,7 +82,7 @@ on:
# #
PRERELEASE:
description: "🧪 Build RC (Pre-release)"
description: '🧪 Build RC (Pre-release)'
required: true
default: false
type: boolean
@@ -69,10 +94,10 @@ on:
# #
VERSION_RC:
description: "🧪 RC (Pre-release) Ver (tvapp2-rc.v1)"
description: '🧪 RC (Pre-release) Ver (tvapp2-rc.v1)'
required: false
type: string
default: "1"
default: '1'
# #
# environment variables
@@ -84,8 +109,11 @@ env:
SHOW_UNCATEGORIZED: false
PRERELEASE: false
VERSION_RC: '1'
ASSIGN_USER: Aetherinox
BOT_NAME_1: EuropaServ
BOT_NAME_2: BinaryServ
BOT_NAME_DEPENDABOT: dependabot[bot]
BOT_NAME_RENOVATE: renovate[bot]
# #
# Jobs
@@ -112,7 +140,7 @@ jobs:
# Initialize Start
# #
- name: "✅ Start"
- name: '✅ Start'
id: task_initialize_start
run: |
echo "Starting build"
@@ -134,24 +162,28 @@ jobs:
# Initialize Checkout
# #
- name: "☑️ Checkout"
- name: '☑️ Checkout'
id: task_initialize_checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
# #
# Initialize Get version from package.json VERSION value
# Initialize Set Package.json Version
# #
- name: "👁️‍🗨️ Package Version Set"
- 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
- name: "👁️‍🗨️ Package Version Get"
# #
# Initialize Get Package.json Version
# #
- name: '👁️‍🗨️ Package Version Get'
id: task_initialize_package_version_get
run: |
echo "VERSION: ${{ steps.task_initialize_package_getversion.outputs.PACKAGE_VERSION }}"
@@ -179,7 +211,7 @@ jobs:
# Release Checkout
# #
- name: "☑️ Checkout"
- name: '☑️ Checkout'
id: task_release_checkout
uses: actions/checkout@v4
with:
@@ -202,7 +234,7 @@ jobs:
# Release Print Version Debug
# #
- name: "🪪 Test Next Job Version"
- name: '🪪 Test Next Job Version'
id: task_release_debug_print_ver
run: |
echo "VERSION: ${{ env.PACKAGE_VERSION }}"
@@ -211,11 +243,12 @@ jobs:
# Release Install package via NPM
# #
- name: "🪪 NPM Install"
- name: '🪪 NPM Install & Lint'
id: task_release_npm_install
working-directory: ./tvapp2
run: |
npm ci
npm run lint
env:
NODE_AUTH_TOKEN: ${{ secrets.SELF_TOKEN_CL }}
@@ -223,39 +256,39 @@ jobs:
# Release Execute npm generate so that a uuid and guid can be created
# #
- name: "🪪 Generate IDs"
- name: '🪪 Generate IDs'
id: task_release_npm_env_generate
working-directory: ./tvapp2
run: |
npm run root:generate
# #
# .ENV Get
# Release .ENV Get
# Get guid and uuid from env variable generated by npm
# #
- name: "🪪 .ENV Get"
- name: '🪪 .ENV Get'
id: task_release_dotenv_get
uses: falti/dotenv-action@v1
with:
path: "./tvapp2/.env"
# #
# .ENV Print (Debug)
# Release .ENV Print (Debug)
# Show guid and uuid from env variable generated by npm
# #
- name: "🪪 .ENV Read"
- 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 }}"
# #
# Build Project & Create Zip
# Release Build Stable
# #
- name: "🔨 Build Stable ( ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip )"
- name: '🔨 Build Stable ( ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip )'
id: task_release_build_st
if: ${{ startsWith( inputs.PRERELEASE, false ) }}
run: |
@@ -267,7 +300,11 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.ADMINSERV_TOKEN_CL }}
- name: "🔨 Build RC ( ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip )"
# #
# Release Build Release Candidate
# #
- name: '🔨 Build RC ( ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip )'
id: task_release_build_rc
if: ${{ startsWith( inputs.PRERELEASE, true ) }}
run: |
@@ -280,14 +317,14 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.ADMINSERV_TOKEN_CL }}
# #
# [ Tag ]: Pre Create
# Release Tag Pre Create
#
# in order to use the changelog github action, you must pre-create the tag otherwise
# the changelog action will have no idea what tag you are going to be creating and
# the list of commits will not be for the correct release.
# #
- name: "🔖 Tag Pre Create ${{ env.PACKAGE_VERSION }}"
- name: '🔖 Tag Pre Create ${{ env.PACKAGE_VERSION }}'
id: task_release_tag_create
uses: rickstaa/action-create-tag@v1
with:
@@ -298,41 +335,64 @@ jobs:
gpg_passphrase: ${{ secrets.ADMINSERV_GPG_PASSPHRASE }}
# #
# [ Tag ]: Confirm
# Release Tag Confirm
#
# check if tag already exists
# #
- name: "🔖 Tag Confirm ${{ env.PACKAGE_VERSION }}"
- 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 }}"
# #
# Checksum Generate
# Release Checksum Stable
# #
- name: "🆔 Checksum Stable"
- name: '🆔 Checksum Stable'
id: task_release_checksum_st_set
if: ${{ startsWith( inputs.PRERELEASE, false ) }}
run: |
filename_zip="${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip"
sha256="$(shasum --algorithm 256 ${filename_zip} | awk '{ print $1 }')"
shasum --algorithm 256 ${filename_zip} > SHA256SUMS.txt
# import gpg key (base64)
echo '${{ secrets.ADMINSERV_GPG_KEY_B64 }}' | base64 -d | gpg --import
# get sha1 and sha256 for .zip and .gz files
find . -maxdepth 1 \( -name '*.zip' -o -name '*.gz' \) -printf '%P\n' | xargs -r sha1sum | gpg --digest-algo 256 --clearsign > sha1sum.txt.asc
find . -maxdepth 1 \( -name '*.zip' -o -name '*.gz' \) -printf '%P\n' | xargs -r sha256sum | gpg --digest-algo sha256 --clearsign > sha256sum.txt.asc
# get sha1sum; assign to variable
sha1sum="$(shasum --algorithm 1 ${filename_zip} | awk '{ print $1 }')"
echo "SHA1SUM=${sha1sum}" >> $GITHUB_ENV
# get sha256sum; assign to variable
sha256sum="$(shasum --algorithm 256 ${filename_zip} | awk '{ print $1 }')"
echo "SHA256SUM=${sha256sum}" >> $GITHUB_ENV
# no longer needed, replaced by find . command
# shasum --algorithm 256 ${filename_zip} > SHA256SUMS.txt
echo "FILE_ZIP=${filename_zip}" >> $GITHUB_ENV
echo "SHA256SUM=${sha256}" >> $GITHUB_ENV
filename_compose_zip="${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-docker-compose.zip"
sha256_compose="$(shasum --algorithm 256 ${filename_compose_zip} | awk '{ print $1 }')"
sha256sum_compose="$(shasum --algorithm 256 ${filename_compose_zip} | awk '{ print $1 }')"
echo "FILE_COMPOSE_ZIP=${filename_compose_zip}" >> $GITHUB_ENV
- name: "🆔 Checksum RC"
# #
# Release Checksum Release Candidate
# #
- name: '🆔 Checksum RC'
id: task_release_checksum_rc_set
if: ${{ startsWith( inputs.PRERELEASE, true ) }}
run: |
# get filename
filename_zip="${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip"
# get sha256 checksum
sha256="$(shasum --algorithm 256 ${filename_zip} | awk '{ print $1 }')"
# write sha256sum to file
shasum --algorithm 256 ${filename_zip} > SHA256SUMS.txt
echo "FILE_ZIP=${filename_zip}" >> $GITHUB_ENV
echo "SHA256SUM=${sha256}" >> $GITHUB_ENV
@@ -342,19 +402,19 @@ jobs:
echo "FILE_COMPOSE_ZIP=${filename_compose_zip}" >> $GITHUB_ENV
# #
# Checksum Print
# Release Checksum Print
# #
- name: "🆔 Checksum Print"
- name: '🆔 Checksum Print'
id: task_release_checksum_st_get
run: |
echo ${{ env.SHA256SUM }}
echo "${{ env.SHA256SUM }}"
# #
# Contributor Images
# Release Contributor Images
# #
- name: "🥸 Contributors Generate"
- name: '🥸 Contributors Generate'
id: task_release_contribs_generate
uses: jaywcjlove/github-action-contributors@main
with:
@@ -363,44 +423,54 @@ jobs:
avatarSize: 42
# #
# Checksum Add to ZIP file
# Release Checksum Add (Stable)
# #
- name: "📦 Zip Add Checksum Stable"
- name: '📦 Zip Add Checksum Stable'
id: task_release_zip_st
if: ${{ startsWith( inputs.PRERELEASE, false ) }}
run: |
echo Zipping STABLE Package .zip ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip
zip -jr ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip SHA256SUMS.txt
zip -jr ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip sha1sum.txt.asc sha256sum.txt.asc
ls
- name: "📦 Zip Add Checksum RC"
# #
# Release Checksum Add (Release Candidate)
# #
- name: '📦 Zip Add Checksum RC'
id: task_release_zip_rc
if: ${{ startsWith( inputs.PRERELEASE, true ) }}
run: |
echo Zipping PRE-RELEASE Package .zip ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip
zip -jr ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip SHA256SUMS.txt
zip -jr ${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip sha1sum.txt.asc sha256sum.txt.asc
ls
# #
# Changelog Generate
#
# generates a changelog from the github api. requires a PREVIOUS_TAG in order to figure
# out the changes made between the two versions.
#
# outputs:
# ${{ steps.changelog.outputs.changelog }}
# Release Generate Tags
# #
- name: "📝 Changelog Pre Setup (Categorized Commits)"
- 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
- name: "📝 Changelog Build (Categorized)"
# #
# Release Changelog Build (Categorized)
#
# generates a changelog from the github api. requires a TAG_LAST in order to figure
# out the changes made between the two versions.
#
# outputs:
# ${{ steps.task_release_changelog_categorized.outputs.changelog }}
# #
- name: '📝 Changelog Build (Categorized)'
id: task_release_changelog_categorized
if: ${{ startsWith( inputs.SHOW_UNCATEGORIZED, false ) }}
if: |
startsWith( inputs.SHOW_UNCATEGORIZED, false ) ||
startsWith( env.SHOW_UNCATEGORIZED, false )
uses: mikepenz/release-changelog-builder-action@v5
with:
token: ${{ secrets.ADMINSERV_TOKEN }}
@@ -408,7 +478,7 @@ jobs:
#toTag: "${{ github.ref }}"
configuration: ".github/changelog-configuration.json"
ignorePreReleases: false
commitMode: ${{ inputs.CHANGELOG_MODE_COMMIT }}
commitMode: ${{ inputs.CHANGELOG_MODE_COMMIT || env.CHANGELOG_MODE_COMMIT }}
fetchReleaseInformation: true
fetchViaCommits: true
configurationJson: |
@@ -419,14 +489,24 @@ jobs:
GITHUB_TOKEN: ${{ secrets.ADMINSERV_TOKEN }}
# #
# Release Changelog Build (Uncategorized)
#
# generates a changelog from the github api. requires a TAG_LAST in order to figure
# out the changes made between the two versions.
#
# outputs:
# ${{ steps.task_release_changelog_categorized.outputs.changelog }}
#
# shows only categorized commits using the commit standards
# type(scope): description
# type: description
# #
- name: "📝 Changelog Build (Uncategorized)"
- name: '📝 Changelog Build (Uncategorized)'
id: task_release_changelog_uncategorized
if: ${{ startsWith( inputs.SHOW_UNCATEGORIZED, true ) }}
if: |
startsWith( inputs.SHOW_UNCATEGORIZED, true ) ||
startsWith( env.SHOW_UNCATEGORIZED, true )
uses: mikepenz/release-changelog-builder-action@v5
with:
token: ${{ secrets.ADMINSERV_TOKEN }}
@@ -434,7 +514,7 @@ jobs:
#toTag: "${{ github.ref }}"
configuration: ".github/changelog-configuration.json"
ignorePreReleases: false
commitMode: ${{ inputs.CHANGELOG_MODE_COMMIT }}
commitMode: ${{ inputs.CHANGELOG_MODE_COMMIT || env.CHANGELOG_MODE_COMMIT }}
fetchReleaseInformation: true
fetchViaCommits: true
configurationJson: |
@@ -445,21 +525,53 @@ jobs:
GITHUB_TOKEN: ${{ secrets.ADMINSERV_TOKEN }}
# #
# Changelog Fetch
# Release Changelog Convert step into ENV
#
# This is a requirement in order for the action mikepenz/release-changelog-builder-action@v5 to work properly.
# If you use special characters like quotes and tildes in your push comments, bash will have no way of knowing
# if it's part of the changelog, or code itself.
#
# By converting the step into an env var, we quote the text, and it fixes the issue.
#
# For every step that you need to print the changelog text, first define the env var
# env:
# CHANGELOG_CATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
#
# Then you can call the changelog in the body / run command with
# echo "$CHANGELOG_CATEGORIZED"
# #
- name: "📝 Changelog Print (Categorized)"
if: ${{ startsWith( inputs.SHOW_UNCATEGORIZED, false ) }}
- name: '🙊 Changelog Step to Env Categorized'
id: task_release_changelog_escape_categorized
if: |
startsWith( inputs.SHOW_UNCATEGORIZED, false ) ||
startsWith( env.SHOW_UNCATEGORIZED, false )
env:
CHANGELOG_CATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
run: |
echo "${{ steps.task_release_changelog_categorized.outputs.changelog }}"
echo "$CHANGELOG_CATEGORIZED"
- name: "📝 Changelog Print (Uncategorized)"
if: ${{ startsWith( inputs.SHOW_UNCATEGORIZED, true ) }}
- name: '🙊 Changelog Step to Env Uncategorized'
id: task_release_changelog_escape_uncategorized
if: |
startsWith( inputs.SHOW_UNCATEGORIZED, true ) ||
startsWith( env.SHOW_UNCATEGORIZED, true )
env:
CHANGELOG_UNCATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
run: |
echo "${{ steps.task_release_changelog_uncategorized.outputs.changelog }}"
echo "$CHANGELOG_UNCATEGORIZED"
# #
# [ Release ]: Post Release
# Release List Tree
# #
- name: '⚙️ Debug Tree Listing'
id: task_release_debug_tree
run: |
tree
# #
# Release Post Release (Stable)
#
# outputs:
# [RELEASE ID]:
@@ -467,10 +579,12 @@ jobs:
# ${{ steps.task_release_bundle_st.outputs.id
# #
- name: "🏳️ Post Stable"
if: ${{ startsWith( inputs.PRERELEASE, false ) }}
uses: softprops/action-gh-release@v2
- name: '🏳️ Post Stable'
id: task_release_bundle_st
if: |
startsWith( inputs.PRERELEASE, false ) ||
startsWith( env.PRERELEASE, false )
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.ADMINSERV_TOKEN_CL }}
with:
@@ -483,16 +597,28 @@ jobs:
files: |
${{ env.PROJECT_NAME }}-v${{ env.PACKAGE_VERSION }}-docker-compose.zip
${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}.zip
SHA256SUMS.txt
sha1sum.txt.asc
sha256sum.txt.asc
prerelease: false
body: |
${{ steps.task_release_changelog_categorized.outputs.changelog }}
${{ steps.task_release_changelog_uncategorized.outputs.changelog }}
# #
# Release Post Release (Release Candidate)
#
# outputs:
# [RELEASE ID]:
# ${{ steps.task_release_bundle_rc.outputs.id
# ${{ steps.task_release_bundle_st.outputs.id
# #
- name: "🏳️ Post Release Candidate"
if: ${{ startsWith( inputs.PRERELEASE, true ) }}
uses: softprops/action-gh-release@v2
id: task_release_bundle_rc
if: |
startsWith( inputs.PRERELEASE, true ) ||
startsWith( env.PRERELEASE, true )
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.ADMINSERV_TOKEN }}
with:
@@ -505,7 +631,8 @@ jobs:
files: |
${{ env.PROJECT_NAME }}-v${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}-docker-compose.zip
${{ env.PROJECT_NAME }}-${{ env.PACKAGE_VERSION }}-rc.${{ inputs.VERSION_RC }}.zip
SHA256SUMS.txt
sha1sum.txt.asc
sha256sum.txt.asc
prerelease: false
body: |
> [!WARNING]
@@ -515,17 +642,27 @@ jobs:
${{ steps.task_release_changelog_uncategorized.outputs.changelog }}
# #
# Print Status
# Release Print Status
#
# For every step that you need to print the changelog text, first define the env var
# env:
# CHANGELOG_CATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
#
# Then you can call the changelog in the body / run command with
# echo "$CHANGELOG_CATEGORIZED"
# #
- name: "🎛️ Status Print"
id: task_release_status_print
env:
CHANGELOG_CATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
CHANGELOG_UNCATEGORIZED: ${{ steps.task_release_changelog_categorized.outputs.changelog }}
run: |
echo "Printing Variables"
echo
echo "---- CHANGELOG ---------------------------------------------------------------"
echo "${{ steps.task_release_changelog_categorized.outputs.changelog }}"
echo "${{ steps.task_changelog_uncategorized.outputs.changelog }}"
echo "$CHANGELOG_CATEGORIZED"
echo "$CHANGELOG_UNCATEGORIZED"
echo "---- CHANGELOG ---------------------------------------------------------------"
echo ""
echo ""
@@ -539,7 +676,7 @@ jobs:
echo "---- CHANGELOG ---------------------------------------------------------------"
# #
# Upload artifacts > release files
# Release Upload Artifacts Release Files
# #
- name: >-
@@ -568,7 +705,7 @@ jobs:
steps:
# #
# Job Complete Download Artifacts
# Complete Download Artifacts
# #
- name: "📁 Download Saved Artifacts"
@@ -576,7 +713,7 @@ jobs:
uses: actions/download-artifact@v4
# #
# Job Complete Get publish timestamp
# Complete Get publish timestamp
# #
- name: >-
@@ -589,7 +726,7 @@ jobs:
echo "NOW_DOCKER_LABEL=$(date +'%Y%m%d')" >> $GITHUB_ENV
# #
# Job > Complete > Set ENVs
# Complete Set ENVs
# #
- name: "🕛 Get Env Vars"
@@ -602,7 +739,7 @@ jobs:
echo "SHA_STABLE=${release_stable_sha256}" >> $GITHUB_ENV
# #
# Job Complete Summary of publish
# Complete Summary of publish
# #
- name: "🆗 Completed: ${{ env.NOW }}"

0
.nojekyll Normal file
View File

58
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,58 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
<br />
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
<br />
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
<br />
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
<br />
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
<br />
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at `thebinaryninja [at] proton [dot] me`. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
<br />
## Attribution
This Code of Conduct is adapted from the [📄 Contributor Covenant][homepage], version 2.1, available at [https://contributor-covenant.org/version/2/1/][version]
[homepage]: http://contributor-covenant.org
[version]: https://www.contributor-covenant.org/version/2/1/

643
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,643 @@
<div align="center">
<h6>Contribution Guidelines for BinaryNinja Applications</h6>
<h1>♾️ Contributing ♾️</h1>
<br />
<!-- prettier-ignore-start -->
[![Version][github-version-img]][github-version-uri]
[![Downloads][github-downloads-img]][github-downloads-uri]
[![Size][github-size-img]][github-size-img]
[![Last Commit][github-commit-img]][github-commit-img]
[![Contributors][contribs-all-img]](#contributors-)
<!-- prettier-ignore-end -->
</div>
<br />
---
<br />
## About
Below are a list of ways that you can help contribute to this project, as well as policies and guides that explain how to get started.
Please review everything on this page before you submit your contribution.
<br />
---
<br />
- [About](#about)
- [Issues, Bugs, Ideas](#issues-bugs-ideas)
- [Contributing](#contributing)
- [Before Submitting Pull Requests](#before-submitting-pull-requests)
- [Conventional Commit Specification](#conventional-commit-specification)
- [Types](#types)
- [Example 1:](#example-1)
- [Example 2:](#example-2)
- [Committing](#committing)
- [Languages](#languages)
- [Python](#python)
- [Indentation](#indentation)
- [Line Length](#line-length)
- [Blank Lines](#blank-lines)
- [Imports](#imports)
- [Commenting](#commenting)
- [Casing](#casing)
- [NodeJS](#nodejs)
- [Prettier](#prettier)
- [ESLint](#eslint)
- [v9 \& Newer (Config)](#v9--newer-config)
- [v8 \& Older (Config)](#v8--older-config)
- [Packages](#packages)
- [Indentation](#indentation-1)
- [Style](#style)
- [Line Length](#line-length-1)
- [Commenting](#commenting-1)
- [Casing](#casing-1)
<br />
---
<br />
## Issues, Bugs, Ideas
Stuff happens, and sometimes as best as we try, there may be issues within this project that we are unaware of. That is the great thing about open-source; anyone can use the program and contribute to making it better.
<br />
If you have found a bug, have an issue, or maybe even a cool idea; you can let us know by [submitting it](https://github.com/thebinaryninja/tvapp2/issues). However, before you submit your new issue, bug report, or feature request; head over to the [Issues Section](https://github.com/thebinaryninja/tvapp2/issues) and ensure nobody else has already submitted it.
<br />
Once you are sure that your issue has not already being dealt with; you may submit a new issue at [here](https://github.com/thebinaryninja/tvapp2/issues/new/choose). You'll be asked to specify exactly what your new submission targets, such as:
- Bug report
- Feature Suggestion
<br />
When writing a new submission; ensure you fill out any of the questions asked of you. If you do not provide enough information, we cannot help. Be as detailed as possible, and provide any logs or screenshots you may have to help us better understand what you mean. Failure to fill out the submission properly may result in it being closed without a response.
<br />
If you are submitting a bug report:
- Explain the issue
- Describe how you expect for a feature to work, and what you're seeing instead of what you expected.
- List possible options for a resolution or insight
- Provide screenshots, logs, or anything else that can visually help track down the issue.
<br />
<div align="center">
[![Submit Issue][btn-github-submit-img]][btn-github-submit-uri]
</div>
<br />
<div align="center">
**[`^ back to top ^`](#about)**
</div>
<br />
---
<br />
## Contributing
If you are looking to contribute to this project by actually submit your own code; please review this section completely. There is important information and policies provided below that you must follow for your pull request to get accepted.
The source is here for everyone to collectively share and collaborate on. If you think you have a possible solution to a problem; don't be afraid to get your hands dirty.
All contributions are made via pull requests. To create a pull request, you need a GitHub account. If you are unclear on this process, see [GitHub's documentation on forking and pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). Pull requests should be targeted at the master branch.
<br />
### Before Submitting Pull Requests
- Follow the repository's code formatting conventions (see below);
- Include tests that prove that the change works as intended and does not add regressions;
- Document the changes in the code and/or the project's documentation;
- Your PR must pass the CI pipeline;
- When submitting your Pull Request, use one of the following branches:
- For bug fixes: `main` branch
- For features & functionality: `development` branch
- Include a proper git commit message following the [Conventional Commit Specification](https://conventionalcommits.org/en/v1.0.0/#specification).
<br />
If you have completed the above tasks, the pull request is ready to be reviewed and your pull request's label will be changed to "Ready for Review". At this point, a human will need to step in and manually verify your submission.
Reviewers will approve the pull request once they are satisfied with the patch it will be merged.
<br />
### Conventional Commit Specification
When committing your changes, we require you to follow the [Conventional Commit Specification](https://conventionalcommits.org/en/v1.0.0/#specification). The **Conventional Commits** is a specification for the format and content of a commit message. The concept behind Conventional Commits is to provide a rich commit history that can be read and understood by both humans and automated tools. Conventional Commits have the following format:
<br />
```
<type>[(optional <scope>)]: <description>
[optional <body>]
[optional <footer(s)>]
```
<br />
#### Types
Our repositories make use of the following commit tags:
<br />
| Type | Description |
| --- | --- |
| `feat` | Introduce new feature |
| `fix` | Bug fix |
| `chore` | Includes technical or preventative maintenance task that is necessary for managing the app or repo, such as updating grunt tasks, but is not tied to any specific feature. Usually done for maintenance purposes.<br/>E.g: Edit .gitignore, .prettierrc, .prettierignore, .gitignore, eslint.config.js file |
| `revert` | Revert a previous commit |
| `style` | Update / reformat style of source code. Does not change the way app is implemented. Changes that do not affect the meaning of the code<br />E.g: white-space, formatting, missing semi-colons, change tabs to spaces, etc) |
| `docs` | Change website or markdown documents. Does not mean changes to the documentation generator script itself, only the documents created from the generator. <br/>E.g: documentation, readme.md or markdown |
| `build` | Changes to the build / compilation / packaging process or auxiliary tools such as doc generation<br />E.g: create new build tasks, update release script, etc. |
| `refactor` | Change to production code that leads to no behavior difference,<br/>E.g: split files, rename variables, rename package, improve code style, etc. |
| `test` | Add or refactor tests, no production code change. Changes the suite of automated tests for the app. |
| `ci` | Changes related to Continuous Integration (usually `yml` and other configuration files). |
| `perf` | Performance improvement of algorithms or execution time of the app. Does not change an existing feature. |
<br />
##### Example 1:
```
feat(core): bug affecting menu [#22]
^───^────^ ^────────────────^ ^───^
| | | |
| | | └───⫸ (ISSUE): Reference issue ID
│ │ │
│ │ └──────────────────────⫸ (DESC): Summary in present tense. Use lower case not title case!
│ │
│ └──────────────────────────────⫸ (SCOPE): The package(s) that this change affects
└──────────────────────────────────⫸ (TYPE): See list above
```
<br />
##### Example 2:
```
<type>(<scope>): <short summary> [issue]
| | | |
| | | └─⫸ Reference issue id (optional)
│ │ │
│ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
│ │
│ └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core|
│ elements|forms|http|language-service|localize|platform-browser|
│ platform-browser-dynamic|platform-server|router|service-worker|
│ upgrade|zone.js|packaging|changelog|docs-infra|migrations|ngcc|ve|
│ devtools....
└─⫸ Commit Type: build|ci|doc|docs|feat|fix|perf|refactor|test
website|chore|style|type|revert|deprecate
```
<br />
### Committing
If you are pushing a commit which addresses a submitted issue, reference your issue at the end of the commit message. You may also optionally add the major issue to the end of your commit body.
References should be on their own line, following the word `Ref` or `Refs`
```
Title: fix(core): fix error message displayed to users. [#22]
Description: The description of your commit
Ref: #22, #34, #37
```
<br />
<br />
### Languages
The formatting of code greatly depends on the language being used for this repository. We provide various different languages below as this guide is utilized across multiple repositories.
- [Python](#python)
- [Javascript / Typescript / NodeJS](#nodejs)
<br />
<br />
#### Python
The following guidelines apply to any projects written with Python:
<br />
##### Indentation
Use `4 spaces` per indentation level.
<br />
> [!TIP]
> ✅ Correct
> ```python
> def Encrypt( key : int, bytestr : bytes ):
> res = b''
> i_blk, left_bytes = divmod( len(bytestr), 3 )
> ```
<br />
> [!CAUTION]
> ❌ Wrong
> ```python
> def encrypt( key : int, byteStr : bytes ):
> Res = b''
> iBlk, leftBytes = divmod( len(byteStr), 3 )
> ```
<br />
##### Line Length
Keep the maximum character count to `100 characters per line`. If you are revising old code which doesn't follow this guideline; please rewrite it to conform.
<br />
##### Blank Lines
Surround top-level functions and class definitions with a blank in-between.
Method definitions inside a class are surrounded by a single blank line.
Extra blank lines may be used (sparingly) to separate groups of functions related to one another. Blank lines may be omitted between a bunch of related one-liners (e.g: set of dummy implementations).
<br />
##### Imports
Imports should usually be on separate lines:
<br />
> [!TIP]
> ✅ Correct
> ```python
> import os
> import sys
> ```
<br />
> [!CAUTION]
> ❌ Wrong
> ```python
> import sys, os
> ```
<br />
The following is acceptable:
<br />
> [!TIP]
> ✅ Correct
> ```python
> from mypkg import siblingA, siblingB, siblingC
> ```
<br />
##### Commenting
Comment your code. It helps novice readers to better understand the process. It doesn't have to be painfully obvious explanations, but it helps to give an idea of what something does.
Please append `#` to the beginning of each line.
```python
# #
# byteString : b'1#Aetherx|232#1#233262#0#0#0#'
# #
def Encrypt( key : int, byteString : bytes ):
res = bytearray( )
```
<br />
<br />
##### Casing
- Stick to `camelCase`; unless:
- naming functions, capitalize the first letter
- Capitalize enums
- If you see code not conforming with this, please revise it in your pull request.
<br />
> [!TIP]
> ✅ Correct
> ```python
> def Encrypt( key : int, byteStr : bytes ):
> res = b''
> iBlock, leftBytes = divmod( len(byteStr), 3 )
> ```
<br />
> [!CAUTION]
> ❌ Wrong
> ```python
> def encrypt( key : int, bytestr : bytes ):
> res = b''
> i_blk, left_bytes = divmod( len(bytestr), 3 )
> ```
<br />
<br />
<div align="center">
**[`^ back to top ^`](#about)**
</div>
<br />
---
<br />
#### NodeJS
The following allows you to configure ESLint and Prettier.
<br />
<br />
##### Prettier
We have opted to make use of [ESLint](#eslint) over Prettier. We provide a detailed ESLint flag config file with very specific linting rules. Please review that section for more information.
<br />
<br />
##### ESLint
Within the root folder of the repo, there are several configuration files which you should be using within the project. These files dictate how prettier and eslint will behave and what is acceptable / not acceptable.
<br />
Pick the config file below depending on which version of ESLint you are using. The v8 and older `.eslint` may not be there if we have migrated over to an Eslint v9 flat config file:
<br />
###### v9 & Newer (Config)
Our NodeJS applications require that you utilize ESLint v9 or newer which makes use of a flat config structure. You may find a copy of our flat config at the link below:
- [📄 eslint.config.mjs](https://github.com/thebinaryninja/tvapp2/blob/main/tvapp2/eslint.config.mjs)
<br />
###### v8 & Older (Config)
- We no longer utilize any version of ESLint older than version 9.
<br />
<br />
> [!NOTE]
> When submitting your pull request, these linting and style rules will be verified with all of your files. If you did not follow these rules; the linter tests on your pull request will fail; and you'll be expected to correct these issues before your submission will be transferred over for human review.
<br />
<br />
##### Packages
We use the following packages for linting and prettier.
<br />
| Package | Repo File | Description |
| --- | --- | --- |
| [@stylistic/eslint-plugin-js](https://npmjs.com/package/@stylistic/eslint-plugin-js) | [package.json](./package.json) | JavaScript stylistic rules for ESLint, migrated from eslint core. |
| [@stylistic/eslint-plugin-ts](https://npmjs.com/package/@stylistic/eslint-plugin-ts) | [package.json](./package.json) | TypeScript stylistic rules for ESLint, migrated from typescript-eslint. |
| [@stylistic/eslint-plugin-plus](https://npmjs.com/package/@stylistic/eslint-plugin-plus) | [package.json](./package.json) | Supplementary rules introduced by ESLint Stylistic. |
| [eslint-plugin-prettier](https://npmjs.com/package/eslint-plugin-prettier) | [package.json](./package.json) | Runs Prettier as an ESLint rule and reports differences as individual ESLint issues. |
<br />
You can add the following to your `package.json` file:
https://github.com/TheBinaryNinja/tvapp2/blob/1c75f11e9f0506ad3dd05133fdafc3aeb87686ca/tvapp2/package.json#L81-L92
<br />
<br />
##### Indentation
Use `4 spaces` per indentation level.
<br />
<br />
##### Style
For files that are not controlled by [Prettier](#prettier) or [ESLint](#eslint); use `Allman Style`. Braces should be on their own lines, and any code inside the braces should be indented 4 spaces.
<br />
```javascript
return {
status: "failure",
user:
{
id: "1aaa35aa-fb3a-62ae-ffec-a14g7fc401ac",
label: "Test String",
}
};
while (x == y)
{
foo();
bar();
}
```
<br />
<br />
##### Line Length
Keep the maximum character count to `100 characters per line`. The configs on this page have prettier automatically set up to detect more than 100 characters per line.
<br />
##### Commenting
Comment your code. It helps novice readers to better understand the process. You may use block style commenting, or single lines:
```javascript
/*
tests to decide if the end-user is running on Darwin or another platform.
*/
test(`Return true if platform is Darwin`, () => {
process.platform = 'darwin';
expect(bIsDarwin()).toBe(true);
});
test(`Return false if platform is not Darwin`, () => {
process.platform = 'linux';
expect(bIsDarwin()).toBe(false);
});
```
<br />
##### Casing
Stick to `camelCase` as much as possible.
```javascript
let myVar = 'one';
let secondVar = 'two';
```
<br />
If you are defining a new environment variable; it must be in ALL CAPS in the `Dockerfile`:
```dockerfile
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 STREAM_QUALITY="hd"
ENV FILE_PLAYLIST="playlist.m3u8"
ENV FILE_EPG="xmltv.xml"
ENV LOG_LEVEL=4
ENV TZ="Etc/UTC"
```
<br />
Then you may call your new environment variable within the Javascript code; and ensure you define a default value to correct any user misconfigurations:
```javascript
const envUrlRepo = process.env.URL_REPO || 'https://git.binaryninja.net/binaryninja';
const envStreamQuality = process.env.STREAM_QUALITY || 'hd';
const envFileM3U = process.env.FILE_PLAYLIST || 'playlist.m3u8';
const envFileXML = process.env.FILE_EPG || 'xmltv.xml';
const envFileTAR = process.env.FILE_TAR || 'xmltv.xml.gz';
```
<br />
<br />
<div align="center">
**[`^ back to top ^`](#about)**
</div>
<br />
<br />
<br />
<br />
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- BADGE > GENERAL -->
[general-npmjs-uri]: https://npmjs.com
[general-nodejs-uri]: https://nodejs.org
[general-npmtrends-uri]: http://npmtrends.com/tvapp2
<!-- BADGE > VERSION > GITHUB -->
[github-version-img]: https://img.shields.io/github/v/tag/thebinaryninja/tvapp2?logo=GitHub&label=Version&color=ba5225
[github-version-uri]: https://github.com/thebinaryninja/tvapp2/releases
<!-- BADGE > LICENSE > MIT -->
[license-mit-img]: https://img.shields.io/badge/MIT-FFF?logo=creativecommons&logoColor=FFFFFF&label=License&color=9d29a0
[license-mit-uri]: https://github.com/thebinaryninja/tvapp2/blob/main/LICENSE
<!-- BADGE > GITHUB > DOWNLOAD COUNT -->
[github-downloads-img]: https://img.shields.io/github/downloads/thebinaryninja/tvapp2/total?logo=github&logoColor=FFFFFF&label=Downloads&color=376892
[github-downloads-uri]: https://github.com/thebinaryninja/tvapp2/releases
<!-- BADGE > GITHUB > DOWNLOAD SIZE -->
[github-size-img]: https://img.shields.io/github/repo-size/thebinaryninja/tvapp2?logo=github&label=Size&color=59702a
[github-size-uri]: https://github.com/thebinaryninja/tvapp2/releases
<!-- BADGE > ALL CONTRIBUTORS -->
[contribs-all-img]: https://img.shields.io/github/all-contributors/thebinaryninja/tvapp2?logo=contributorcovenant&color=de1f6f&label=contributors
[contribs-all-uri]: https://github.com/all-contributors/all-contributors
<!-- BADGE > GITHUB > BUILD > NPM -->
[github-build-img]: https://img.shields.io/github/actions/workflow/status/thebinaryninja/tvapp2/npm-release.yml?logo=github&logoColor=FFFFFF&label=Build&color=%23278b30
[github-build-uri]: https://github.com/thebinaryninja/tvapp2/actions/workflows/npm-release.yml
<!-- BADGE > GITHUB > BUILD > Pypi -->
[github-build-pypi-img]: https://img.shields.io/github/actions/workflow/status/thebinaryninja/tvapp2/release-pypi.yml?logo=github&logoColor=FFFFFF&label=Build&color=%23278b30
[github-build-pypi-uri]: https://github.com/thebinaryninja/tvapp2/actions/workflows/pypi-release.yml
<!-- BADGE > GITHUB > TESTS -->
[github-tests-img]: https://img.shields.io/github/actions/workflow/status/thebinaryninja/tvapp2/npm-tests.yml?logo=github&label=Tests&color=2c6488
[github-tests-uri]: https://github.com/thebinaryninja/tvapp2/actions/workflows/npm-tests.yml
<!-- BADGE > GITHUB > COMMIT -->
[github-commit-img]: https://img.shields.io/github/last-commit/thebinaryninja/tvapp2?logo=conventionalcommits&logoColor=FFFFFF&label=Last%20Commit&color=313131
[github-commit-uri]: https://github.com/thebinaryninja/tvapp2/commits/main/
<!-- BADGE > Github > Docker Image > SELFHOSTED BADGES -->
[github-docker-version-img]: https://badges-ghcr.onrender.com/thebinaryninja/tvapp2/latest_tag?color=%233d9e18&ignore=development-amd64%2Cdevelopment%2Cdevelopment-arm64%2Clatest&label=version&trim=
[github-docker-version-uri]: https://github.com/thebinaryninja/tvapp2/pkgs/container/tvapp2
<!-- BADGE > Dockerhub > Docker Image -->
[dockerhub-docker-version-img]: https://img.shields.io/docker/v/thebinaryninja/tvapp2?sort=semver&arch=arm64
[dockerhub-docker-version-uri]: https://hub.docker.com/repository/docker/thebinaryninja/tvapp2/general
<!-- BADGE > Gitea > Docker Image > SELFHOSTED BADGES -->
[gitea-docker-version-img]: https://badges-ghcr.onrender.com/thebinaryninja/tvapp2/latest_tag?color=%233d9e18&ignore=latest&label=version&trim=
[gitea-docker-version-uri]: https://git.binaryninja.net/BinaryNinja/tvapp2
<!-- BADGE > Gitea 2 > Docker Image -->
[gitea2-docker-version-img]: https://img.shields.io/gitea/v/release/binaryninja/tvapp2?gitea_url=https%3A%2F%2Fgit.binaryninja.net
[gitea2-docker-version-uri]: https://git.binaryninja.net/BinaryNinja/-/packages/container/tvapp2/latest
<!-- BADGE > BUTTON > SUBMIT ISSUES -->
[btn-github-submit-img]: https://img.shields.io/badge/submit%20new%20issue-de1f5c?style=for-the-badge&logo=github&logoColor=FFFFFF
[btn-github-submit-uri]: https://github.com/thebinaryninja/tvapp2/issues
<!-- prettier-ignore-end -->
<!-- markdownlint-restore -->

View File

@@ -9,55 +9,103 @@
# https://git.binaryninja.net/BinaryNinja/tvapp2
# https://github.com/aetherinox/docker-base-alpine
#
# you can build your own image by running
# amd64 docker build --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 -t tvapp2:1.0.0-amd64 -f Dockerfile .
# arm64 docker build --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:1.0.0-arm64 -f Dockerfile.aarch64 .
# build your own image by running
# amd64 docker build --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 -t tvapp2:latest -t tvapp2:1.5.0 -t tvapp2:1.5.0-amd64 -f Dockerfile .
# arm64 docker build --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 -t tvapp2:1.5.0-arm64 -f Dockerfile.aarch64 .
#
# if you prefer to use `docker buildx`
# OR; build using `docker buildx`
# create docker buildx create --driver docker-container --name container --bootstrap --use
# amd64 docker buildx build --no-cache --pull --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 --platform=linux/amd64 --output type=docker --output type=docker .
# arm64 docker buildx build --no-cache --pull --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 --platform=linux/arm64 --output type=docker --output type=docker .
# amd64 docker buildx build --build-arg ARCH=amd64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0-amd64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/amd64 --output type=docker --allow network.host --network host --no-cache --pull --push .
# arm64 docker buildx build --build-arg ARCH=arm64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0-arm64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/arm64 --output type=docker --allow network.host --network host --no-cache --pull --push .
#
# OR; build single amd64 image
# create docker buildx create --driver docker-container --name container --bootstrap --use
# amd64 docker buildx build --build-arg ARCH=amd64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0 --tag ghcr.io/thebinaryninja/tvapp2:1.5 --tag ghcr.io/thebinaryninja/tvapp2:1 --tag ghcr.io/thebinaryninja/tvapp2:latest --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/amd64 --output type=docker --allow network.host --network host --no-cache --push .
#
# OR; build official image (publish)
# create docker buildx create --driver docker-container --name container --bootstrap --use
# amd64-stable docker buildx build --build-arg ARCH=amd64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0-amd64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/amd64 --output type=docker --allow network.host --network host --no-cache --pull --push .
# arm64-stable docker buildx build --build-arg ARCH=arm64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=stable --tag ghcr.io/thebinaryninja/tvapp2:1.5.0-arm64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/arm64 --output type=docker --allow network.host --network host --no-cache --pull --push .
# amd64-dev docker buildx build --build-arg ARCH=amd64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=development --tag ghcr.io/thebinaryninja/tvapp2:development-amd64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/amd64 --output type=docker --allow network.host --network host --no-cache --pull --push .
# arm64-dev docker buildx build --build-arg ARCH=arm64 --build-arg VERSION=1.5.0 --build-arg BUILDDATE=20260812 --build-arg RELEASE=development --tag ghcr.io/thebinaryninja/tvapp2:development-arm64 --attest type=provenance,disabled=true --attest type=sbom,disabled=true --file Dockerfile --platform linux/arm64 --output type=docker --allow network.host --network host --no-cache --pull --push .
# amd64-stable-hash docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.0-amd64
# arm64-stable-hash docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:1.5.0-arm64
# amd64-dev-hash docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-amd64
# arm64-dev-hash docker buildx imagetools inspect ghcr.io/thebinaryninja/tvapp2:development-arm64
# merge-stable docker buildx imagetools create --tag ghcr.io/thebinaryninja/tvapp2:1.5.0 --tag ghcr.io/thebinaryninja/tvapp2:1.5 --tag ghcr.io/thebinaryninja/tvapp2:1 --tag ghcr.io/thebinaryninja/tvapp2:latest sha256:0abe1b1c119959b3b1ccc23c56a7ee2c4c908c6aaef290d4ab2993859d807a3b sha256:e68b9de8669eac64d4e4d2a8343c56705e05e9a907cf0b542343f9b536d9c473
# merge-dev docker buildx imagetools create --tag ghcr.io/thebinaryninja/tvapp2:development sha256:8f36385a28c8f6eb7394d903c9a7a2765b06f94266b32628389ee9e3e3d7e69d sha256:c719ccb034946e3f0625003f25026d001768794e38a1ba8aafc9146291d548c5
# #
FROM ghcr.io/aetherinox/alpine-base:3.20-amd64
# #
# FROM
# any args defined before FROM cannot be called after FROM and the ARE is classified outside the build process.
# You will have to re-define the arg after FROM to utilize it anywhere else in the build process.
#
# @ref https://docs.docker.com/reference/dockerfile/#understand-how-arg-and-from-interact
# #
ARG ARCH=amd64
ARG ALPINE_VERSION=3.21
FROM --platform=linux/${ARCH} ghcr.io/aetherinox/alpine-base:${ALPINE_VERSION}
# #
# Set Args
# #
ARG ARCH=amd64
ARG ALPINE_VERSION=3.21
ARG BUILDDATE
ARG VERSION
ARG RELEASE
# #
# Set Labels
# #
LABEL maintainer="aetherinox, iFlip721"
LABEL org.opencontainers.image.authors="aetherinox, iFlip721"
LABEL org.opencontainers.image.authors="Aetherinox, iFlip721, Optx"
LABEL org.opencontainers.image.vendor="BinaryNinja"
LABEL org.opencontainers.image.title="TVApp2"
LABEL org.opencontainers.image.description="Automatic m3u and xml guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client."
LABEL org.opencontainers.image.source="https://github.com/TheBinaryNinja/tvapp2"
LABEL org.opencontainers.image.repo.1="https://github.com/TheBinaryNinja/tvapp2"
LABEL org.opencontainers.image.repo.2="https://git.binaryninja.net/BinaryNinja/tvapp2"
LABEL org.opencontainers.image.source="https://github.com/thebinaryninja/tvapp2"
LABEL org.opencontainers.image.repo.1="https://github.com/thebinaryninja/tvapp2"
LABEL org.opencontainers.image.repo.2="https://git.binaryninja.net/binaryninja/tvapp2"
LABEL org.opencontainers.image.repo.3="https://github.com/aetherinox/docker-base-alpine"
LABEL org.opencontainers.image.documentation="https://github.com/TheBinaryNinja/tvapp2/wiki"
LABEL org.opencontainers.image.url="https://github.com/TheBinaryNinja/tvapp2/pkgs/container/tvapp2"
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 BUILDVERSION="TVApp2 v${VERSION} Build ${BUILDDATE}"
LABEL org.opencontainers.image.architecture="${ARCH}"
LABEL org.opencontainers.image.ref.name="main"
LABEL org.opencontainers.image.registry="local"
LABEL org.opencontainers.image.release="${RELEASE}"
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}"
# #
# Set Env Var
# #
ENV TZ="Etc/UTC"
ENV URL_REPO="https://git.binaryninja.net/BinaryNinja/"
ENV WEB_IP="0.0.0.0"
ENV WEB_PORT=4124
ENV NODE_VERSION=18.20.5
ENV NODE_VERSION=22.8.0
ENV YARN_VERSION=1.22.22
ENV RELEASE="${RELEASE}"
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 WEB_ENCODING="deflate, br"
ENV WEB_PROXY_HEADER="x-forwarded-for"
ENV STREAM_QUALITY="hd"
ENV FILE_URL="urls.txt"
ENV FILE_M3U="playlist.m3u8"
ENV FILE_EPG="xmltv.xml"
ENV FILE_TAR="xmltv.xml.gz"
ENV HEALTH_TIMER=600000
ENV TASK_CRON_SYNC="0 0 */3 * *"
ENV LOG_LEVEL=4
ENV TZ="Etc/UTC"
# #
# Install
@@ -66,6 +114,7 @@ ENV DIR_RUN=/usr/bin/app
RUN \
apk add --no-cache \
wget \
curl \
bash \
nano \
npm \

View File

@@ -1,115 +0,0 @@
# syntax=docker/dockerfile:1
# #
# @project TVApp2
# @usage docker image which allows you to download a m3u playlist and EPG guide data from
# multiple IPTV services.
# @file Dockerfile.aarch64
# @repo https://github.com/TheBinaryNinja/tvapp2
# https://git.binaryninja.net/BinaryNinja/tvapp2
# https://github.com/aetherinox/docker-base-alpine
#
# you can build your own image by running
# amd64 docker build --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 -t tvapp2:1.0.0-amd64 -f Dockerfile .
# arm64 docker build --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:1.0.0-arm64 -f Dockerfile.aarch64 .
#
# if you prefer to use `docker buildx`
# create docker buildx create --driver docker-container --name container --bootstrap --use
# amd64 docker buildx build --no-cache --pull --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 --platform=linux/amd64 --output type=docker --output type=docker .
# arm64 docker buildx build --no-cache --pull --build-arg VERSION=1.0.0 --build-arg BUILDDATE=20250218 -t tvapp2:latest -t tvapp2:1.0.0 --platform=linux/arm64 --output type=docker --output type=docker .
# #
FROM ghcr.io/aetherinox/alpine-base:3.20-arm64
# #
# Set Args
# #
ARG BUILDDATE
ARG VERSION
# #
# Set Labels
# #
LABEL maintainer="aetherinox, iFlip721"
LABEL org.opencontainers.image.authors="aetherinox, iFlip721"
LABEL org.opencontainers.image.vendor="BinaryNinja"
LABEL org.opencontainers.image.title="TVApp2"
LABEL org.opencontainers.image.description="Automatic m3u and xml guide updater for TheTvApp, TVPass, and MoveOnJoy utilized within your IPTV client."
LABEL org.opencontainers.image.source="https://github.com/TheBinaryNinja/tvapp2"
LABEL org.opencontainers.image.repo.1="https://github.com/TheBinaryNinja/tvapp2"
LABEL org.opencontainers.image.repo.2="https://git.binaryninja.net/BinaryNinja/tvapp2"
LABEL org.opencontainers.image.repo.3="https://github.com/aetherinox/docker-base-alpine"
LABEL org.opencontainers.image.documentation="https://github.com/TheBinaryNinja/tvapp2/wiki"
LABEL org.opencontainers.image.url="https://github.com/TheBinaryNinja/tvapp2/pkgs/container/tvapp2"
LABEL org.opencontainers.image.licenses="MIT"
LABEL BUILDVERSION="TVApp2 v${VERSION} Build ${BUILDDATE}"
# #
# Set Env Var
# #
ENV TZ="Etc/UTC"
ENV URL_REPO="https://git.binaryninja.net/BinaryNinja/"
ENV WEB_IP="0.0.0.0"
ENV WEB_PORT=4124
ENV NODE_VERSION=18.20.5
ENV YARN_VERSION=1.22.22
ENV DIR_BUILD=/usr/src/app
ENV DIR_RUN=/usr/bin/app
# #
# Install
# #
RUN \
apk add --no-cache \
wget \
bash \
nano \
npm \
openssl
# #
# Copy docker-entrypoint
# #
COPY docker-entrypoint.sh /usr/local/bin/
# #
# copy s6-overlays root to image root
# #
COPY root/ /
# #
# set work directory
# #
WORKDIR ${DIR_BUILD}
# #
# copy tvapp2 project to workdir
# #
COPY tvapp2/ ./
# #
# set work dir to built app
# #
WORKDIR ${DIR_RUN}
# #
# Ports and volumes
# #
EXPOSE ${WEB_PORT}/tcp
# #
# In case user sets up the cron for a longer duration, do a first run
# and then keep the container running. Hacky, but whatever.
# #
ENTRYPOINT ["/init"]

1062
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -44,3 +44,7 @@ services:
memlock:
soft: -1
hard: -1
healthcheck:
test: [ "CMD", "curl", "--fail", "http://127.0.0.1:4124" ]
interval: 30s
retries: 5

View File

@@ -6,21 +6,197 @@ tags:
# Changelog
This section outlines all releases of TVApp2, including the version of the release, and the changes made for that release.
<br />
<p align="center" markdown="1">
![Version](https://img.shields.io/github/v/tag/TheBinaryNinja/tvapp2?logo=GitHub&label=version&color=ba5225)
![Downloads](https://img.shields.io/github/downloads/TheBinaryNinja/tvapp2/total)
![Repo Size](https://img.shields.io/github/repo-size/TheBinaryNinja/tvapp2?label=size&color=59702a)
![Last Commit)](https://img.shields.io/github/last-commit/TheBinaryNinja/tvapp2?color=b43bcc)
<!-- prettier-ignore-start -->
[![Version][github-version-img]][github-version-uri]
[![Downloads][github-downloads-img]][github-downloads-uri]
[![Size][github-size-img]][github-size-img]
[![Last Commit][github-commit-img]][github-commit-img]
[![Contributors][contribs-all-img]](#contributors)
<!-- prettier-ignore-end -->
</p>
### <!-- md:version stable- --> 1.0.0 <small>Feb 24, 2025</small> { id="1.0.0" }
<br />
---
<br />
### <!-- md:version stable- --> 1.4.0 <small>Apr 10, 2025</small> { id="1.4.0" }
- `feat`: add new environment variable `HEALTH_TIMER` _(See feature below)_
- `feat`: new health check icon in top right; triggers every `10 minutes` and reports back to the web interface as a toast notification
- health check shows timer until next health check is performed.
- `feat`: added bootstramp toast notifications and modals
- `feat`: new modal dialog when when user triggers a re-sync of the m3u and epg data
- `feat`: footer of web interface how discloses which build is being ran; `stable` or `development`
- `refactor`: console logging system overhauled with new syntax for how logs are displayed
- `refactor`: additional logs added for `LOG_LEVEL=5` or higher
<br />
---
<br />
### <!-- md:version stable- --> 1.3.0 <small>Apr 9, 2025</small> { id="1.3.0" }
- `feat`: new button to upper-right side of header which allows you to **force re-sync** your m3u and xml data.
- `feat`: new api endpoint `/api/resync`; utilized to resync M3U and EPG data
- `feat`: new api endpoint `/api/health`; utilized in your `docker-compose.yml` health check
- `feat`: new env variable `WEB_ENCODING`; allows you to customize the HTTP `Accept-Encoding` request and response header
- `feat`: new env variable `IP_GATEWAY`; stores assigned container gateway ip
- `feat`: new env variable `IP_CONTAINER`; stores assigned container ip
- `refactor`: env variables re-named
- `FILE_TAR``FILE_GZP`
- `refactor`: errors and success messages now use api endpoint; lists timestamp, error code, etc.
- `build`: bump bootstrap from `v4` to `v5`
- `fix`: bug in Jellyfin which caused EPG data syncing to error out for hosts which cannot support gzip compression
- 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._
<br />
---
<br />
### <!-- md:version stable- --> 1.2.0 <small>Apr 5, 2025</small> { id="1.2.0" }
- `feat`: add support for additional mime types and default file type
- `style`: rename env variable `FILE_PLAYLIST` to `FILE_M3U`
- `build`: add new env vars to `Dockerfile`
- `FILE_TAR`
- `FILE_URL`
- `refactor`: m3u and epg files now stored in `www` folder
- `fix`: html template links pointing to old repo
<br />
---
<br />
### <!-- md:version stable- --> 1.1.0 <small>Mar 25, 2025</small> { id="1.1.0" }
- `feat`: new interface & theme for web ui
- integrated bootstrap 4.x
- new dark theme
- all hosted files now display `date`, `download url`, `size`, and `description`
- automatic viewport resizing depending on the device used
- new favicon
- localized css, js, and image support instead of relying on externally hosted resources
- `feat`: app now offers a compressed `gzip` for EPG guide data
- `feat`: add multiple url paths to download each asset
- `/guide`, `/epg`, `xml`
- `/playlist`, `/m3u`, `/m3u8`
- `/gzip`, `/gz`
- `feat`: add mew environment variables
- `LOG_LEVEL`: specifies what level of logs you will see in console
- `STREAM_QUALITY`: specifies the quality of the stream; options: `hd` and `sd`
- `FILE_PLAYLIST`: filename that M3U playlist data will be stored to
- `FILE_EPG`: filename that EPG / XML guide data will be stored to
- `FILE_GZIP`: filename that compressed gzip guide data will be stored to
- `build`: app migrated from CommonJS to ES Modules
- `build`: bump alpine base image from v3.20 to v3.21
- `build`: migrated html template to independent file in `www` folder; utilizes `ejs` module
- `build`: **amd64** and **arm64** docker images merged into one image with architecture support
- `fix`: resolved bug where local server could not be started using nodejs; related to ip address variable
- `build(deps)`: add package `ejs` 3.1.10
- `build(deps)`: add package `chalk` 5.3.0
- `build(deps)`: add package `moment` 2.30.1
- `build(deps-dev)`: add package `eslint` 9.17.0
- `build(deps-dev)`: add package `eslint-plugin-chai-friendly` 1.0.1
- `build(deps-dev)`: add package `eslint-plugin-import` 2.31.0
- `build(deps-dev)`: add package `eslint-plugin-n` 17.15.0
- `build(deps-dev)`: add package `eslint-plugin-promise` 7.2.1
- `build(deps-dev)`: add package `@stylistic/eslint-plugin-js` 3.1.0
- `remove`: tvapp2.fonts.min.js
<br />
---
<br />
### <!-- md:version stable- --> 1.0.0 <small>Feb 24, 2025</small> { id="1.0.0" }
- Initial release
<br />
---
<br />
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- BADGE > GENERAL -->
[general-npmjs-uri]: https://npmjs.com
[general-nodejs-uri]: https://nodejs.org
[general-npmtrends-uri]: http://npmtrends.com/csf-firewall
<!-- BADGE > VERSION > GITHUB -->
[github-version-img]: https://img.shields.io/github/v/tag/TheBinaryNinja/tvapp2?logo=GitHub&label=Version&color=ba5225
[github-version-uri]: https://github.com/TheBinaryNinja/tvapp2/releases
<!-- BADGE > LICENSE > MIT -->
[license-mit-img]: https://img.shields.io/badge/MIT-FFF?logo=creativecommons&logoColor=FFFFFF&label=License&color=9d29a0
[license-mit-uri]: https://github.com/TheBinaryNinja/tvapp2/blob/main/LICENSE
<!-- BADGE > GITHUB > DOWNLOAD COUNT -->
[github-downloads-img]: https://img.shields.io/github/downloads/TheBinaryNinja/tvapp2/total?logo=github&logoColor=FFFFFF&label=Downloads&color=376892
[github-downloads-uri]: https://github.com/TheBinaryNinja/tvapp2/releases
<!-- BADGE > GITHUB > DOWNLOAD SIZE -->
[github-size-img]: https://img.shields.io/github/repo-size/TheBinaryNinja/tvapp2?logo=github&label=Size&color=59702a
[github-size-uri]: https://github.com/TheBinaryNinja/tvapp2/releases
<!-- BADGE > ALL CONTRIBUTORS -->
[contribs-all-img]: https://img.shields.io/github/all-contributors/TheBinaryNinja/tvapp2?logo=contributorcovenant&color=de1f6f&label=contributors
[contribs-all-uri]: https://github.com/all-contributors/all-contributors
<!-- BADGE > GITHUB > BUILD > NPM -->
[github-build-img]: https://img.shields.io/github/actions/workflow/status/TheBinaryNinja/tvapp2/npm-release.yml?logo=github&logoColor=FFFFFF&label=Build&color=%23278b30
[github-build-uri]: https://github.com/TheBinaryNinja/tvapp2/actions/workflows/npm-release.yml
<!-- BADGE > GITHUB > BUILD > Pypi -->
[github-build-pypi-img]: https://img.shields.io/github/actions/workflow/status/TheBinaryNinja/tvapp2/release-pypi.yml?logo=github&logoColor=FFFFFF&label=Build&color=%23278b30
[github-build-pypi-uri]: https://github.com/TheBinaryNinja/tvapp2/actions/workflows/pypi-release.yml
<!-- BADGE > GITHUB > TESTS -->
[github-tests-img]: https://img.shields.io/github/actions/workflow/status/TheBinaryNinja/tvapp2/npm-tests.yml?logo=github&label=Tests&color=2c6488
[github-tests-uri]: https://github.com/TheBinaryNinja/tvapp2/actions/workflows/npm-tests.yml
<!-- BADGE > GITHUB > COMMIT -->
[github-commit-img]: https://img.shields.io/github/last-commit/TheBinaryNinja/tvapp2?logo=conventionalcommits&logoColor=FFFFFF&label=Last%20Commit&color=313131
[github-commit-uri]: https://github.com/TheBinaryNinja/tvapp2/commits/main/
<!-- BADGE > Github > Docker Image > SELFHOSTED BADGES -->
[github-docker-version-img]: https://badges-ghcr.onrender.com/thebinaryninja/tvapp2/latest_tag?color=%233d9e18&ignore=development-amd64%2Cdevelopment%2Cdevelopment-arm64%2Clatest&label=version&trim=
[github-docker-version-uri]: https://github.com/TheBinaryNinja/tvapp2/pkgs/container/tvapp2
<!-- BADGE > Dockerhub > Docker Image -->
[dockerhub-docker-version-img]: https://img.shields.io/docker/v/thebinaryninja/tvapp2?sort=semver&arch=arm64
[dockerhub-docker-version-uri]: https://hub.docker.com/repository/docker/thebinaryninja/tvapp2/general
<!-- BADGE > Gitea > Docker Image > SELFHOSTED BADGES -->
[gitea-docker-version-img]: https://badges-ghcr.onrender.com/thebinaryninja/tvapp2/latest_tag?color=%233d9e18&ignore=latest&label=version&trim=
[gitea-docker-version-uri]: https://git.binaryninja.net/BinaryNinja/tvapp2
<!-- BADGE > Gitea 2 > Docker Image -->
[gitea2-docker-version-img]: https://img.shields.io/gitea/v/release/binaryninja/tvapp2?gitea_url=https%3A%2F%2Fgit.binaryninja.net
[gitea2-docker-version-uri]: https://git.binaryninja.net/BinaryNinja/-/packages/container/tvapp2/latest
<!-- BADGE > BUTTON > SUBMIT ISSUES -->
[btn-github-submit-img]: https://img.shields.io/badge/submit%20new%20issue-de1f5c?style=for-the-badge&logo=github&logoColor=FFFFFF
[btn-github-submit-uri]: https://github.com/TheBinaryNinja/tvapp2/issues
<!-- prettier-ignore-end -->
<!-- markdownlint-restore -->

View File

@@ -0,0 +1,652 @@
---
title: Contributing to TVApp2
tags:
- info
---
<div align="center" markdown="1">
<h1>♾️ Contributing ♾️</h1>
<br />
<p align="center" markdown="1">
<!-- prettier-ignore-start -->
[![Version][github-version-img]][github-version-uri]
[![Downloads][github-downloads-img]][github-downloads-uri]
[![Size][github-size-img]][github-size-img]
[![Last Commit][github-commit-img]][github-commit-img]
[![Contributors][contribs-all-img]](#contributors)
<!-- prettier-ignore-end -->
</p>
</div>
<br />
---
<br />
## About
Below are a list of ways that you can help contribute to this project, as well as policies and guides that explain how to get started.
Please review everything on this page before you submit your contribution.
<br />
---
<br />
- [About](#about)
- [Issues, Bugs, Ideas](#issues-bugs-ideas)
- [Contributing](#contributing)
- [Before Submitting Pull Requests](#before-submitting-pull-requests)
- [Conventional Commit Specification](#conventional-commit-specification)
- [Types](#types)
- [Example 1:](#example-1)
- [Example 2:](#example-2)
- [Committing](#committing)
- [Languages](#languages)
- [Python](#python)
- [Indentation](#indentation)
- [Line Length](#line-length)
- [Blank Lines](#blank-lines)
- [Imports](#imports)
- [Commenting](#commenting)
- [Casing](#casing)
- [NodeJS](#nodejs)
- [Prettier](#prettier)
- [ESLint](#eslint)
- [v9 \& Newer (Config)](#v9--newer-config)
- [v8 \& Older (Config)](#v8--older-config)
- [Packages](#packages)
- [Indentation](#indentation-1)
- [Style](#style)
- [Line Length](#line-length-1)
- [Commenting](#commenting-1)
- [Casing](#casing-1)
<br />
---
<br />
## Issues, Bugs, Ideas
Stuff happens, and sometimes as best as we try, there may be issues within this project that we are unaware of. That is the great thing about open-source; anyone can use the program and contribute to making it better.
<br />
If you have found a bug, have an issue, or maybe even a cool idea; you can let us know by [submitting it](https://github.com/thebinaryninja/tvapp2/issues). However, before you submit your new issue, bug report, or feature request; head over to the [Issues Section](https://github.com/thebinaryninja/tvapp2/issues) and ensure nobody else has already submitted it.
<br />
Once you are sure that your issue has not already being dealt with; you may submit a new issue at [here](https://github.com/thebinaryninja/tvapp2/issues/new/choose). You'll be asked to specify exactly what your new submission targets, such as:
- Bug report
- Feature Suggestion
<br />
When writing a new submission; ensure you fill out any of the questions asked of you. If you do not provide enough information, we cannot help. Be as detailed as possible, and provide any logs or screenshots you may have to help us better understand what you mean. Failure to fill out the submission properly may result in it being closed without a response.
<br />
If you are submitting a bug report:
- Explain the issue
- Describe how you expect for a feature to work, and what you're seeing instead of what you expected.
- List possible options for a resolution or insight
- Provide screenshots, logs, or anything else that can visually help track down the issue.
<br />
<div align="center">
[![Submit Issue][btn-github-submit-img]][btn-github-submit-uri]
</div>
<br />
<div align="center">
**[`^ back to top ^`](#about)**
</div>
<br />
---
<br />
## Contributing
If you are looking to contribute to this project by actually submit your own code; please review this section completely. There is important information and policies provided below that you must follow for your pull request to get accepted.
The source is here for everyone to collectively share and collaborate on. If you think you have a possible solution to a problem; don't be afraid to get your hands dirty.
All contributions are made via pull requests. To create a pull request, you need a GitHub account. If you are unclear on this process, see [GitHub's documentation on forking and pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). Pull requests should be targeted at the master branch.
<br />
### Before Submitting Pull Requests
- Follow the repository's code formatting conventions (see below);
- Include tests that prove that the change works as intended and does not add regressions;
- Document the changes in the code and/or the project's documentation;
- Your PR must pass the CI pipeline;
- When submitting your Pull Request, use one of the following branches:
- For bug fixes: `main` branch
- For features & functionality: `development` branch
- Include a proper git commit message following the [Conventional Commit Specification](https://conventionalcommits.org/en/v1.0.0/#specification).
<br />
If you have completed the above tasks, the pull request is ready to be reviewed and your pull request's label will be changed to "Ready for Review". At this point, a human will need to step in and manually verify your submission.
Reviewers will approve the pull request once they are satisfied with the patch it will be merged.
<br />
### Conventional Commit Specification
When committing your changes, we require you to follow the [Conventional Commit Specification](https://conventionalcommits.org/en/v1.0.0/#specification). The **Conventional Commits** is a specification for the format and content of a commit message. The concept behind Conventional Commits is to provide a rich commit history that can be read and understood by both humans and automated tools. Conventional Commits have the following format:
<br />
```
<type>[(optional <scope>)]: <description>
[optional <body>]
[optional <footer(s)>]
```
<br />
#### Types
Our repositories make use of the following commit tags:
<br />
| Type | Description |
| --- | --- |
| `feat` | <sup><sub>Introduce new feature</sub></sup> |
| `fix` | <sup><sub>Bug fix</sub></sup> |
| `chore` | <sup><sub>Includes technical or preventative maintenance task that is necessary for managing the app or repo, such as updating grunt tasks, but is not tied to any specific feature. Usually done for maintenance purposes.<br/>E.g: Edit .gitignore, .prettierrc, .prettierignore, .gitignore, eslint.config.js file</sub></sup> |
| `revert` | <sup><sub>Revert a previous commit</sub></sup> |
| `style` | <sup><sub>Update / reformat style of source code. Does not change the way app is implemented. Changes that do not affect the meaning of the code<br />E.g: white-space, formatting, missing semi-colons, change tabs to spaces, etc)</sub></sup> |
| `docs` | <sup><sub>Change website or markdown documents. Does not mean changes to the documentation generator script itself, only the documents created from the generator. <br/>E.g: documentation, readme.md or markdown |
| `build` | <sup><sub>Changes to the build / compilation / packaging process or auxiliary tools such as doc generation<br />E.g: create new build tasks, update release script, etc.</sub></sup> |
| `refactor` | <sup><sub>Change to production code that leads to no behavior difference,<br/>E.g: split files, rename variables, rename package, improve code style, etc.</sub></sup> |
| `test` | <sup><sub>Add or refactor tests, no production code change. Changes the suite of automated tests for the app.</sub></sup> |
| `ci` | <sup><sub>Changes related to Continuous Integration (usually `yml` and other configuration files).</sub></sup> |
| `perf` | <sup><sub>Performance improvement of algorithms or execution time of the app. Does not change an existing feature.</sub></sup> |
<br />
##### Example 1:
```
feat(core): bug affecting menu [#22]
^───^────^ ^────────────────^ ^───^
| | | |
| | | └───⫸ (ISSUE): Reference issue ID
│ │ │
│ │ └──────────────────────⫸ (DESC): Summary in present tense. Use lower case not title case!
│ │
│ └──────────────────────────────⫸ (SCOPE): The package(s) that this change affects
└──────────────────────────────────⫸ (TYPE): See list above
```
<br />
##### Example 2:
```
<type>(<scope>): <short summary> [issue]
| | | |
| | | └─⫸ Reference issue id (optional)
│ │ │
│ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
│ │
│ └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core|
│ elements|forms|http|language-service|localize|platform-browser|
│ platform-browser-dynamic|platform-server|router|service-worker|
│ upgrade|zone.js|packaging|changelog|docs-infra|migrations|ngcc|ve|
│ devtools....
└─⫸ Commit Type: build|ci|doc|docs|feat|fix|perf|refactor|test
website|chore|style|type|revert|deprecate
```
<br />
### Committing
If you are pushing a commit which addresses a submitted issue, reference your issue at the end of the commit message. You may also optionally add the major issue to the end of your commit body.
References should be on their own line, following the word `Ref` or `Refs`
```
Title: fix(core): fix error message displayed to users. [#22]
Description: The description of your commit
Ref: #22, #34, #37
```
<br />
<br />
### Languages
The formatting of code greatly depends on the language being used for this repository. We provide various different languages below as this guide is utilized across multiple repositories.
- [Python](#python)
- [Javascript / Typescript / NodeJS](#nodejs)
<br />
<br />
#### Python
The following guidelines apply to any projects written with Python:
<br />
##### Indentation
Use `4 spaces` per indentation level.
<br />
> [!TIP]
> ✅ Correct
> ```python
> def Encrypt( key : int, bytestr : bytes ):
> res = b''
> i_blk, left_bytes = divmod( len(bytestr), 3 )
> ```
<br />
> [!CAUTION]
> ❌ Wrong
> ```python
> def encrypt( key : int, byteStr : bytes ):
> Res = b''
> iBlk, leftBytes = divmod( len(byteStr), 3 )
> ```
<br />
##### Line Length
Keep the maximum character count to `100 characters per line`. If you are revising old code which doesn't follow this guideline; please rewrite it to conform.
<br />
##### Blank Lines
Surround top-level functions and class definitions with a blank in-between.
Method definitions inside a class are surrounded by a single blank line.
Extra blank lines may be used (sparingly) to separate groups of functions related to one another. Blank lines may be omitted between a bunch of related one-liners (e.g: set of dummy implementations).
<br />
##### Imports
Imports should usually be on separate lines:
<br />
> [!TIP]
> ✅ Correct
> ```python
> import os
> import sys
> ```
<br />
> [!CAUTION]
> ❌ Wrong
> ```python
> import sys, os
> ```
<br />
The following is acceptable:
<br />
> [!TIP]
> ✅ Correct
> ```python
> from mypkg import siblingA, siblingB, siblingC
> ```
<br />
##### Commenting
Comment your code. It helps novice readers to better understand the process. It doesn't have to be painfully obvious explanations, but it helps to give an idea of what something does.
Please append `#` to the beginning of each line.
```python
# #
# byteString : b'1#Aetherx|232#1#233262#0#0#0#'
# #
def Encrypt( key : int, byteString : bytes ):
res = bytearray( )
```
<br />
<br />
##### Casing
- Stick to `camelCase`; unless:
- naming functions, capitalize the first letter
- Capitalize enums
- If you see code not conforming with this, please revise it in your pull request.
<br />
> [!TIP]
> ✅ Correct
> ```python
> def Encrypt( key : int, byteStr : bytes ):
> res = b''
> iBlock, leftBytes = divmod( len(byteStr), 3 )
> ```
<br />
> [!CAUTION]
> ❌ Wrong
> ```python
> def encrypt( key : int, bytestr : bytes ):
> res = b''
> i_blk, left_bytes = divmod( len(bytestr), 3 )
> ```
<br />
<br />
<div align="center">
**[`^ back to top ^`](#about)**
</div>
<br />
---
<br />
#### NodeJS
The following allows you to configure ESLint and Prettier.
<br />
<br />
##### Prettier
We have opted to make use of [ESLint](#eslint) over Prettier. We provide a detailed ESLint flag config file with very specific linting rules. Please review that section for more information.
<br />
<br />
##### ESLint
Within the root folder of the repo, there are several configuration files which you should be using within the project. These files dictate how prettier and eslint will behave and what is acceptable / not acceptable.
<br />
Pick the config file below depending on which version of ESLint you are using. The v8 and older `.eslint` may not be there if we have migrated over to an Eslint v9 flat config file:
<br />
###### v9 & Newer (Config)
Our NodeJS applications require that you utilize ESLint v9 or newer which makes use of a flat config structure. You may find a copy of our flat config at the link below:
- [📄 eslint.config.mjs](https://github.com/thebinaryninja/tvapp2/blob/main/tvapp2/eslint.config.mjs)
<br />
###### v8 & Older (Config)
- We no longer utilize any version of ESLint older than version 9.
<br />
<br />
> [!NOTE]
> When submitting your pull request, these linting and style rules will be verified with all of your files. If you did not follow these rules; the linter tests on your pull request will fail; and you'll be expected to correct these issues before your submission will be transferred over for human review.
<br />
<br />
##### Packages
We use the following packages for linting and prettier.
<br />
| Package | Repo File | Description |
| --- | --- | --- |
| [@stylistic/eslint-plugin-js](https://npmjs.com/package/@stylistic/eslint-plugin-js) | [package.json](./package.json) | JavaScript stylistic rules for ESLint, migrated from eslint core. |
| [@stylistic/eslint-plugin-ts](https://npmjs.com/package/@stylistic/eslint-plugin-ts) | [package.json](./package.json) | TypeScript stylistic rules for ESLint, migrated from typescript-eslint. |
| [@stylistic/eslint-plugin-plus](https://npmjs.com/package/@stylistic/eslint-plugin-plus) | [package.json](./package.json) | Supplementary rules introduced by ESLint Stylistic. |
| [eslint-plugin-prettier](https://npmjs.com/package/eslint-plugin-prettier) | [package.json](./package.json) | Runs Prettier as an ESLint rule and reports differences as individual ESLint issues. |
<br />
You can add the following to your `package.json` file:
https://github.com/TheBinaryNinja/tvapp2/blob/1c75f11e9f0506ad3dd05133fdafc3aeb87686ca/tvapp2/package.json#L81-L92
<br />
<br />
##### Indentation
Use `4 spaces` per indentation level.
<br />
<br />
##### Style
For files that are not controlled by [Prettier](#prettier) or [ESLint](#eslint); use `Allman Style`. Braces should be on their own lines, and any code inside the braces should be indented 4 spaces.
<br />
```javascript
return {
status: "failure",
user:
{
id: "1aaa35aa-fb3a-62ae-ffec-a14g7fc401ac",
label: "Test String",
}
};
while (x == y)
{
foo();
bar();
}
```
<br />
<br />
##### Line Length
Keep the maximum character count to `100 characters per line`. The configs on this page have prettier automatically set up to detect more than 100 characters per line.
<br />
##### Commenting
Comment your code. It helps novice readers to better understand the process. You may use block style commenting, or single lines:
```javascript
/*
tests to decide if the end-user is running on Darwin or another platform.
*/
test(`Return true if platform is Darwin`, () => {
process.platform = 'darwin';
expect(bIsDarwin()).toBe(true);
});
test(`Return false if platform is not Darwin`, () => {
process.platform = 'linux';
expect(bIsDarwin()).toBe(false);
});
```
<br />
##### Casing
Stick to `camelCase` as much as possible.
```javascript
let myVar = 'one';
let secondVar = 'two';
```
<br />
If you are defining a new environment variable; it must be in ALL CAPS in the `Dockerfile`:
```dockerfile
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 STREAM_QUALITY="hd"
ENV FILE_PLAYLIST="playlist.m3u8"
ENV FILE_EPG="xmltv.xml"
ENV LOG_LEVEL=4
ENV TZ="Etc/UTC"
```
<br />
Then you may call your new environment variable within the Javascript code; and ensure you define a default value to correct any user misconfigurations:
```javascript
const envUrlRepo = process.env.URL_REPO || 'https://git.binaryninja.net/binaryninja';
const envStreamQuality = process.env.STREAM_QUALITY || 'hd';
const envFileM3U = process.env.FILE_PLAYLIST || 'playlist.m3u8';
const envFileXML = process.env.FILE_EPG || 'xmltv.xml';
const envFileTAR = process.env.FILE_TAR || 'xmltv.xml.gz';
```
<br />
<br />
<div align="center">
**[`^ back to top ^`](#about)**
</div>
<br />
<br />
<br />
<br />
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- BADGE > GENERAL -->
[general-npmjs-uri]: https://npmjs.com
[general-nodejs-uri]: https://nodejs.org
[general-npmtrends-uri]: http://npmtrends.com/tvapp2
<!-- BADGE > VERSION > GITHUB -->
[github-version-img]: https://img.shields.io/github/v/tag/thebinaryninja/tvapp2?logo=GitHub&label=Version&color=ba5225
[github-version-uri]: https://github.com/thebinaryninja/tvapp2/releases
<!-- BADGE > LICENSE > MIT -->
[license-mit-img]: https://img.shields.io/badge/MIT-FFF?logo=creativecommons&logoColor=FFFFFF&label=License&color=9d29a0
[license-mit-uri]: https://github.com/thebinaryninja/tvapp2/blob/main/LICENSE
<!-- BADGE > GITHUB > DOWNLOAD COUNT -->
[github-downloads-img]: https://img.shields.io/github/downloads/thebinaryninja/tvapp2/total?logo=github&logoColor=FFFFFF&label=Downloads&color=376892
[github-downloads-uri]: https://github.com/thebinaryninja/tvapp2/releases
<!-- BADGE > GITHUB > DOWNLOAD SIZE -->
[github-size-img]: https://img.shields.io/github/repo-size/thebinaryninja/tvapp2?logo=github&label=Size&color=59702a
[github-size-uri]: https://github.com/thebinaryninja/tvapp2/releases
<!-- BADGE > ALL CONTRIBUTORS -->
[contribs-all-img]: https://img.shields.io/github/all-contributors/thebinaryninja/tvapp2?logo=contributorcovenant&color=de1f6f&label=contributors
[contribs-all-uri]: https://github.com/all-contributors/all-contributors
<!-- BADGE > GITHUB > BUILD > NPM -->
[github-build-img]: https://img.shields.io/github/actions/workflow/status/thebinaryninja/tvapp2/npm-release.yml?logo=github&logoColor=FFFFFF&label=Build&color=%23278b30
[github-build-uri]: https://github.com/thebinaryninja/tvapp2/actions/workflows/npm-release.yml
<!-- BADGE > GITHUB > BUILD > Pypi -->
[github-build-pypi-img]: https://img.shields.io/github/actions/workflow/status/thebinaryninja/tvapp2/release-pypi.yml?logo=github&logoColor=FFFFFF&label=Build&color=%23278b30
[github-build-pypi-uri]: https://github.com/thebinaryninja/tvapp2/actions/workflows/pypi-release.yml
<!-- BADGE > GITHUB > TESTS -->
[github-tests-img]: https://img.shields.io/github/actions/workflow/status/thebinaryninja/tvapp2/npm-tests.yml?logo=github&label=Tests&color=2c6488
[github-tests-uri]: https://github.com/thebinaryninja/tvapp2/actions/workflows/npm-tests.yml
<!-- BADGE > GITHUB > COMMIT -->
[github-commit-img]: https://img.shields.io/github/last-commit/thebinaryninja/tvapp2?logo=conventionalcommits&logoColor=FFFFFF&label=Last%20Commit&color=313131
[github-commit-uri]: https://github.com/thebinaryninja/tvapp2/commits/main/
<!-- BADGE > Github > Docker Image > SELFHOSTED BADGES -->
[github-docker-version-img]: https://badges-ghcr.onrender.com/thebinaryninja/tvapp2/latest_tag?color=%233d9e18&ignore=development-amd64%2Cdevelopment%2Cdevelopment-arm64%2Clatest&label=version&trim=
[github-docker-version-uri]: https://github.com/thebinaryninja/tvapp2/pkgs/container/tvapp2
<!-- BADGE > Dockerhub > Docker Image -->
[dockerhub-docker-version-img]: https://img.shields.io/docker/v/thebinaryninja/tvapp2?sort=semver&arch=arm64
[dockerhub-docker-version-uri]: https://hub.docker.com/repository/docker/thebinaryninja/tvapp2/general
<!-- BADGE > Gitea > Docker Image > SELFHOSTED BADGES -->
[gitea-docker-version-img]: https://badges-ghcr.onrender.com/thebinaryninja/tvapp2/latest_tag?color=%233d9e18&ignore=latest&label=version&trim=
[gitea-docker-version-uri]: https://git.binaryninja.net/BinaryNinja/tvapp2
<!-- BADGE > Gitea 2 > Docker Image -->
[gitea2-docker-version-img]: https://img.shields.io/gitea/v/release/binaryninja/tvapp2?gitea_url=https%3A%2F%2Fgit.binaryninja.net
[gitea2-docker-version-uri]: https://git.binaryninja.net/BinaryNinja/-/packages/container/tvapp2/latest
<!-- BADGE > BUTTON > SUBMIT ISSUES -->
[btn-github-submit-img]: https://img.shields.io/badge/submit%20new%20issue-de1f5c?style=for-the-badge&logo=github&logoColor=FFFFFF
[btn-github-submit-uri]: https://github.com/thebinaryninja/tvapp2/issues
<!-- prettier-ignore-end -->
<!-- markdownlint-restore -->

View File

@@ -9,8 +9,9 @@ conventions:
The tag symbol in conjunction with a version number denotes when a specific feature or behavior was added. Make sure you're at least on this version if you want to use it.
: <!-- md:version --> default
: <!-- md:version stable- --> stable
: <!-- md:version beta- --> beta
: <!-- md:version development- --> development
<br />
@@ -19,11 +20,13 @@ The tag symbol in conjunction with a version number denotes when a specific feat
These icons define what type of control a specified setting uses.
: <!-- md:control toggle --> toggle
: <!-- md:control toggle_on --> toggle on
: <!-- md:control toggle_off --> toggle off
: <!-- md:control textbox --> textbox
: <!-- md:control dropdown --> dropdown
: <!-- md:control button --> button
: <!-- md:control slider --> slider
: <!-- md:control color --> color wheel
: <!-- md:control color #E5E5E5 #121315 --> color wheel
<br />
@@ -45,40 +48,42 @@ This defines a command
<br />
### <!-- md:flag experimental --> Experimental { data-toc-label="Experimental" }
### <!-- md:flag --> Flags { #flags data-toc-label="Flags" }
: <!-- md:flag --> Default
: <!-- md:flag experimental --> Experimental
: <!-- md:flag required --> Required
: <!-- md:flag customization --> Customization
: <!-- md:flag metadata --> Metadata
: <!-- md:flag dangerous --> Dangerous
: <!-- md:flag multiple --> Multiple
: <!-- md:flag setting --> Setting
Anything listed with this icon are features or functionality that are still in development and may change in future versions.
<br />
### <!-- md:flag required --> Required value { #required data-toc-label="Required value" }
Items listed with this symbol indicate that they are required to be set.
<br />
### <!-- md:flag customization --> Customization { #customization data-toc-label="Customization" }
This symbol denotes that the item described is a customization which affects the overall look of the app.
<br />
### <!-- md:3rdparty --> 3rd Party { data-toc-label="3rd Party" }
This symbol denotes that the item described is classified as something that changes the overall functionality of the plugin.
<br />
### <!-- md:flag metadata --> Metadata property { #metadata data-toc-label="Metadata property" }
### <!-- md:flag setting --> Configurable Settings { #setting data-toc-label="Configurable Setting" }
This symbol denotes that the item described is a metadata property, which can
be used in Markdown documents as part of the front matter definition.
The following denotes a configurable setting. These can also be broken up into individual settings as shown below:
<br />
#### <!-- md:setting example.setting.enabled -->
<!-- md:version 1.0.0 -->
<!-- md:default `true` -->
### <!-- md:flag setting --> Configurable Setting { #setting data-toc-label="Configurable Setting" }
This is an example setting
This symbol denotes that an item is configurable by the user
#### <!-- md:setting example.setting.other -->
<!-- md:version 1.0.0 -->
<!-- md:default `true` -->
This is another example setting
<br />
@@ -91,9 +96,23 @@ can be used multiple times in the `plugins` setting in `mkdocs.yml`.
### <!-- md:feature --> Optional feature { #feature data-toc-label="Optional feature" }
Most of the features are hidden behind feature flags, which means they must
be explicitly enabled via `mkdocs.yml`. This allows for the existence of
potentially orthogonal features.
Some features may be hidden behind feature flags, which means they must
be explicitly enabled first before they can be configured. This allows
for the existence of potentially orthogonal features.
<br />
### <!-- md:markdown --> Markdown extension { data-toc-label="Markdown extension" #extension }
This symbol denotes that the thing described is a Markdown element.
<br />
### <!-- md:flag dangerous --> Dangerous { #danger data-toc-label="Dangerous Property" }
This symbol denotes that the item described is a metadata property, which can
be used in Markdown documents as part of the front matter definition.
<br />
@@ -107,3 +126,58 @@ available to backers. Normal users will not have access to this particular item.
---
<br />
## Other Tags
<br />
### Example Download { #example data-toc-label="Dangerous Property" }
<!-- md:example my-example-file -->
The following denotes a downloadable file.
<br />
### Commands
<!-- md:command `-s, --start` -->
The above denotes a command which can be executed in a terminal / command prompt.
<br />
## Options
Options are another form of setting which lists what the option does, and then examples of how it works.
<!-- md:option rss.enabled -->
: <!-- md:default `true` --> This option specifies whether
the plugin is enabled when building your project. If you want to speed up
local builds, you can use an [environment variable][mkdocs.env]:
``` yaml
plugins:
- rss:
enabled: !ENV [CI, false]
```
<!-- md:option rss.match_path -->
: <!-- md:default `.*` --> This option specifies which
pages should be included in the feed. For example, to only include blog
posts in the feed, use the following regular expression:
``` yaml
plugins:
- rss:
match_path: blog/posts/.*
```
<br />
---
<br />
<br />
<br />

View File

@@ -0,0 +1,236 @@
---
title: About TVApp2
tags:
- info
---
# About
<figure markdown="span">
![Image settings](https://raw.githubusercontent.com/TheBinaryNinja/tvapp2/main/docs/img/screenshots/01.png){ width="60%" }
<figcaption>TVApp2 Main Interface</figcaption>
</figure>
<br />
<h1 align="center"><b>TVApp2: M3U Playlist & Data Guides</b></h1>
<p align="center" markdown="1">
<!-- prettier-ignore-start -->
[![Version][github-version-img]][github-version-uri]
[![Downloads][github-downloads-img]][github-downloads-uri]
[![Size][github-size-img]][github-size-img]
[![Last Commit][github-commit-img]][github-commit-img]
[![Contributors][contribs-all-img]](#contributors)
<!-- prettier-ignore-end -->
</p>
## What is TVApp2?
**TVApp2** is a docker image which allows you to download M3U playlists and EPG guide data from various online IPTV services such as **TheTVApp**, **TVPass**, and **MoveOnJoy**. The playlist and guide data files can be imported into your favorite IPTV applications such as Jellyfin, Plex, and Emby.
Once the docker container is started; a fresh copy of the channel list and TV guide data will be downloaded and generated within your docker container. You can then visit the website URL associated with your docker container; which will give you direct links to the files that you can utilize with the above listed IPTV apps.
All channels contain multiple sources so that you have a reliable streaming experience, and helps you combat moments when one channel source goes offline.
<br />
---
<br />
## Image Sources
This project contains several repositories which all share the same code; use them as backups:
- [🔀 dockerhub:thebinaryninja/tvapp2](https://hub.docker.com/r/thebinaryninja/tvapp2)
- [🔀 github:thebinaryninja/tvapp2](https://github.com/thebinaryninja/tvapp2)
- [🔀 gitea:git.binaryninja.net/binaryninja/tvapp2](https://git.binaryninja.net/binaryninja/tvapp2)
<br />
| Pull URL | Registry | Arch | Version |
| --- | --- | --- | --- |
| `ghcr.io/thebinaryninja/tvapp2:latest`<br />`ghcr.io/thebinaryninja/tvapp2:development` | Github | amd64<br/>arm64 | [![Github][github-docker-version-img]][github-docker-version-uri] |
| `thebinaryninja/tvapp2:latest`<br />`thebinaryninja/tvapp2:development` | Dockerhub | amd64<br/>arm64 | [![Dockerhub][dockerhub-docker-version-img]][dockerhub-docker-version-uri] |
| `git.binaryninja.net/binaryninja/tvapp2:latest`<br />`git.binaryninja.net/binaryninja/tvapp2:development` | Gitea | amd64<br/>arm64 | [![Gitea][gitea-docker-version-img]][gitea-docker-version-uri] |
<br />
---
<br />
## Features
The following is a small list of the features available with the TVApp2 container:
- Multiple IPTV service sources:
- TheTVApp
- TVPass
- MoveOnJoy
- Channel playlists can be downloaded as a `.m38u` or a compressed `.gzip` archive.
- Compressed gzip compatible with 3rd party apps like Cabernet and Jellyfin.
- Tracking statistics which show the last update time, size, and a description for each file's purpose.
- API endpoints:
- **Resync All Files**
- http://127.0.0.1:4124/api/resync
- http://127.0.0.1:4124/api/restart
- **Health check**
- http://127.0.0.1:4124/api/health
- http://127.0.0.1:4124/api/status
- Direct access to download each of the generated files, including multiple easy-to-remember URLs for each file type.
- **M3U Playlist**:
- http://127.0.0.1:4124/playlist
- http://127.0.0.1:4124/m3u
- http://127.0.0.1:4124/m38u
- **EPG Guide Data (Uncompressed)**:
- http://127.0.0.1:4124/guide
- http://127.0.0.1:4124/epg
- http://127.0.0.1:4124/xml
- **EPG Guide Data (Compressed)**:
- http://127.0.0.1:4124/gzip
- http://127.0.0.1:4124/gz
- Video sources include both quality options `hd` and `sd`.
- Easily mountable docker volumes to access the generated files quickly.
- Wide variety of docker environment variables to change the binding IP, port, app root directory, quality, timezone, etc.
- Small docker image size; based on Alpine 3.x which averages `40MB`.
- Compatible with architectures `amd64` and `arm64/aarm64`.
- Example configurations for 3rd party apps such as Traefik and Authentik.
<br />
---
<br />
## ✨ Contributors
We are always looking for contributors. If you feel that you can provide something useful to Gistr, then we'd love to review your suggestion. Before submitting your contribution, please review the following resources:
- [Pull Request Procedure](https://github.com/TheBinaryNinja/tvapp2/blob/main/.github/PULL_REQUEST_TEMPLATE.md)
- [Contributor Policy](https://github.com/TheBinaryNinja/tvapp2/blob/main/CONTRIBUTING.md)
<br />
Want to help but can't write code?
- Review [active questions by our community](https://github.com/TheBinaryNinja/tvapp2/labels/help%20wanted) and answer the ones you know.
<br />
<div align="center" markdown="1">
![Alt](https://repobeats.axiom.co/api/embed/fb7e11f0bc61b125f923a0ee3eb0bd8aba79b8d5.svg "Repobeats analytics image")
</div>
<br />
The following people have helped get this project going:
<br />
<div align="center" markdown="1">
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![Contributors][contribs-all-img]](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top"><a href="https://github.com/Aetherinox">
<img src="https://avatars.githubusercontent.com/u/118329232?v=4?s=40" width="80px;" alt="Aetherinox"/><br /><sub><b>Aetherinox</b></sub></a><br /><a href="https://github.com/TheBinaryNinja/tvapp2/commits?author=Aetherinox" title="Code">💻</a>
</td>
<td align="center" valign="top"><a href="https://github.com/iFlip721">
<img src="https://avatars.githubusercontent.com/u/28721588?v=4" width="80px;" alt="iFlip721"/><br /><sub><b>iFlip721</b></sub></a><br /><a href="https://github.com/TheBinaryNinja/tvapp2/commits?author=iFlip721" title="Code">💻</a>
</td>
<td align="center" valign="top"><a href="https://github.com/Nvmdfth">
<img src="https://avatars.githubusercontent.com/u/32874812?v=4" width="80px;" alt="Nvmdfth"/><br /><sub><b>Optx</b></sub></a><br /><a href="https://github.com/TheBinaryNinja/tvapp2/commits?author=Nvmdfth" title="Code">💻</a>
</td>
</tr>
</tbody>
</table>
</div>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
<br />
---
<br />
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<!-- BADGE > GENERAL -->
[general-npmjs-uri]: https://npmjs.com
[general-nodejs-uri]: https://nodejs.org
[general-npmtrends-uri]: http://npmtrends.com/csf-firewall
<!-- BADGE > VERSION > GITHUB -->
[github-version-img]: https://img.shields.io/github/v/tag/TheBinaryNinja/tvapp2?logo=GitHub&label=Version&color=ba5225
[github-version-uri]: https://github.com/TheBinaryNinja/tvapp2/releases
<!-- BADGE > LICENSE > MIT -->
[license-mit-img]: https://img.shields.io/badge/MIT-FFF?logo=creativecommons&logoColor=FFFFFF&label=License&color=9d29a0
[license-mit-uri]: https://github.com/TheBinaryNinja/tvapp2/blob/main/LICENSE
<!-- BADGE > GITHUB > DOWNLOAD COUNT -->
[github-downloads-img]: https://img.shields.io/github/downloads/TheBinaryNinja/tvapp2/total?logo=github&logoColor=FFFFFF&label=Downloads&color=376892
[github-downloads-uri]: https://github.com/TheBinaryNinja/tvapp2/releases
<!-- BADGE > GITHUB > DOWNLOAD SIZE -->
[github-size-img]: https://img.shields.io/github/repo-size/TheBinaryNinja/tvapp2?logo=github&label=Size&color=59702a
[github-size-uri]: https://github.com/TheBinaryNinja/tvapp2/releases
<!-- BADGE > ALL CONTRIBUTORS -->
[contribs-all-img]: https://img.shields.io/github/all-contributors/TheBinaryNinja/tvapp2?logo=contributorcovenant&color=de1f6f&label=contributors
[contribs-all-uri]: https://github.com/all-contributors/all-contributors
<!-- BADGE > GITHUB > BUILD > NPM -->
[github-build-img]: https://img.shields.io/github/actions/workflow/status/TheBinaryNinja/tvapp2/npm-release.yml?logo=github&logoColor=FFFFFF&label=Build&color=%23278b30
[github-build-uri]: https://github.com/TheBinaryNinja/tvapp2/actions/workflows/npm-release.yml
<!-- BADGE > GITHUB > BUILD > Pypi -->
[github-build-pypi-img]: https://img.shields.io/github/actions/workflow/status/TheBinaryNinja/tvapp2/release-pypi.yml?logo=github&logoColor=FFFFFF&label=Build&color=%23278b30
[github-build-pypi-uri]: https://github.com/TheBinaryNinja/tvapp2/actions/workflows/pypi-release.yml
<!-- BADGE > GITHUB > TESTS -->
[github-tests-img]: https://img.shields.io/github/actions/workflow/status/TheBinaryNinja/tvapp2/npm-tests.yml?logo=github&label=Tests&color=2c6488
[github-tests-uri]: https://github.com/TheBinaryNinja/tvapp2/actions/workflows/npm-tests.yml
<!-- BADGE > GITHUB > COMMIT -->
[github-commit-img]: https://img.shields.io/github/last-commit/TheBinaryNinja/tvapp2?logo=conventionalcommits&logoColor=FFFFFF&label=Last%20Commit&color=313131
[github-commit-uri]: https://github.com/TheBinaryNinja/tvapp2/commits/main/
<!-- BADGE > Github > Docker Image > SELFHOSTED BADGES -->
[github-docker-version-img]: https://badges-ghcr.onrender.com/thebinaryninja/tvapp2/latest_tag?color=%233d9e18&ignore=development-amd64%2Cdevelopment%2Cdevelopment-arm64%2Clatest&label=version&trim=
[github-docker-version-uri]: https://github.com/TheBinaryNinja/tvapp2/pkgs/container/tvapp2
<!-- BADGE > Dockerhub > Docker Image -->
[dockerhub-docker-version-img]: https://img.shields.io/docker/v/thebinaryninja/tvapp2?sort=semver&arch=arm64
[dockerhub-docker-version-uri]: https://hub.docker.com/repository/docker/thebinaryninja/tvapp2/general
<!-- BADGE > Gitea > Docker Image > SELFHOSTED BADGES -->
[gitea-docker-version-img]: https://badges-ghcr.onrender.com/thebinaryninja/tvapp2/latest_tag?color=%233d9e18&ignore=latest&label=version&trim=
[gitea-docker-version-uri]: https://git.binaryninja.net/BinaryNinja/tvapp2
<!-- BADGE > Gitea 2 > Docker Image -->
[gitea2-docker-version-img]: https://img.shields.io/gitea/v/release/binaryninja/tvapp2?gitea_url=https%3A%2F%2Fgit.binaryninja.net
[gitea2-docker-version-uri]: https://git.binaryninja.net/BinaryNinja/-/packages/container/tvapp2/latest
<!-- BADGE > BUTTON > SUBMIT ISSUES -->
[btn-github-submit-img]: https://img.shields.io/badge/submit%20new%20issue-de1f5c?style=for-the-badge&logo=github&logoColor=FFFFFF
[btn-github-submit-uri]: https://github.com/TheBinaryNinja/tvapp2/issues
<!-- prettier-ignore-end -->
<!-- markdownlint-restore -->

View File

@@ -1,13 +0,0 @@
---
title: About TVApp2
tags:
- info
---
# About TVApp2
<br />
---
<br />

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

521
docs/docs/config/env.md Normal file
View File

@@ -0,0 +1,521 @@
---
title: Environment Variables
tags:
- config
---
# Environment Variables
Environment variables allow you to modify how TVApp2 functions within a docker container. Ensure that
you understand the setting you are changing before you modify these values, otherwise, TVApp2 may fail to
start due to misconfigurations.
<br />
## TZ
<!-- md:control env -->
<!-- md:version stable-1.0.0 -->
<!-- md:default `Etc/UTC` -->
The `TZ` environment variable specifies the timezone that your docker container will
utilize. This is useful for syncing your local time with console outputs such as
our logging system.
=== "Example"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- TZ=Etc/UTC # (1)
```
1. :information: Changing this env variable will change the time for anything
related to the TVApp2 docker container.
=== "Timezones"
``` yaml
Etc/UTC
Africa/Cairo
Africa/Johannesburg
Africa/Lagos
America/Argentina/Buenos_Aires
America/Bogota
America/Caracas
America/Chicago
America/El_Salvador
America/Juneau
America/Lima
America/Los_Angeles
America/Mexico_City
America/New_York
America/Phoenix
America/Santiago
America/Sao_Paulo
America/Toronto
America/Vancouver
Asia/Almaty
Asia/Ashkhabad
Asia/Bahrain
Asia/Bangkok
Asia/Chongqing
Asia/Dubai
Asia/Ho_Chi_Minh
Asia/Hong_Kong
Asia/Jakarta
Asia/Jerusalem
Asia/Kathmandu
Asia/Kolkata
Asia/Kuwait
Asia/Muscat
Asia/Qatar
Asia/Riyadh
Asia/Seoul
Asia/Shanghai
Asia/Singapore
Asia/Taipei
Asia/Tehran
Asia/Tokyo
Atlantic/Reykjavik
Australia/ACT
Australia/Adelaide
Australia/Brisbane
Australia/Sydney
Europe/Athens
Europe/Belgrade
Europe/Berlin
Europe/Copenhagen
Europe/Helsinki
Europe/Istanbul
Europe/London
Europe/Luxembourg
Europe/Madrid
Europe/Moscow
Europe/Paris
Europe/Riga
Europe/Rome
Europe/Stockholm
Europe/Tallinn
Europe/Vilnius
Europe/Warsaw
Europe/Zurich
Pacific/Auckland
Pacific/Chatham
Pacific/Fakaofo
Pacific/Honolulu
Pacific/Norfolk
US/Mountain
```
<br />
## WEB_IP
<!-- md:control env -->
<!-- md:version stable-1.0.0 -->
<!-- md:default `0.0.0.0` -->
The `WEB_IP` environment variable allows you to define what IP address will be
bound to the TVApp2 docker image.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- WEB_IP=0.0.0.0 # (1)
```
1. :information: Specify `0.0.0.0` to bind to all local IP addresses.
<br />
## WEB_PORT
<!-- md:control env -->
<!-- md:version stable-1.0.0 -->
<!-- md:default `4124` -->
The `WEB_PORT` environment variable allows you to define what port the TVApp2
docker container will listen to.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- WEB_IP=4124 # (1)
```
1. :information: The default port is `4124`
<br />
## WEB_ENCODING
<!-- md:control env -->
<!-- md:version stable-1.3.0 -->
<!-- md:default `deflate, br` -->
The `WEB_ENCODING` environment variable allows you to customize the HTTP `Accept-Encoding` request and response header. This value specifies what content encoding the sender can understand when sending these requests.
Most users will not need to modify this value unless you are running Jellyfin and receive the following error when attempting to sync EPG data between Jellyfin and the TVApp2 container:
!!! warning "Jellyfin 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.
```
If you receive the above error and you have already customized this environment variable to include `gzip`, you must remove it from your accepted encoders string to fix the error.
=== "Old"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- WEB_ENCODING: 'gzip, deflate, br'
```
=== "New"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- WEB_ENCODING: 'deflate, br'
```
<br />
## URL_REPO
<!-- md:control env -->
<!-- md:version stable-1.0.0 -->
<!-- md:default `https://git.binaryninja.net/binaryninja` -->
<!-- md:flag dangerous -->
The `URL_REPO` environment variable allows you to specify what Github repo is used to communicate
with in order to fetch IPTV data. This is used as an internal environment variable by the developers
and should not be changed by the end-user; otherwise you will be unable to fetch IPTV data.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- URL_REPO=https://git.binaryninja.net/binaryninja # (1)
```
1. :warning: It is highly recommended that you do not change this value
otherwise you will not be able to download the latest M3U playlists
and EPG guide data.
<br />
## FILE_URL
<!-- md:control env -->
<!-- md:version stable-1.2.0 -->
<!-- md:default `urls.txt` -->
The `FILE_URL` environment variable allows you to specify what the name of the downloaded `urls.txt` cache file. This file is downloaded when you first spin up the TVApp2 container.
There should be no need to utilize this environment variable unless you have a specific reason.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- FILE_URL=urls.txt # (1)
```
1. :warning: There is really no reason to modify this environment variable
unless you have a specific purpose.
<br />
## FILE_M3U
<!-- md:control env -->
<!-- md:version stable-1.1.0 -->
<!-- md:default `playlist.m3u8` -->
The `FILE_M3U` environment variable allows you to specify what the name of the downloaded `playlist.m3u8` file will be. This file is downloaded when you first spin up the TVApp2 container, and contains a list of what channels you can pick from.
There should be no need to utilize this environment variable unless you have a specific reason.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- FILE_M3U=playlist.m3u8 # (1)
```
1. :warning: There is really no reason to modify this environment variable
unless you have a specific purpose.
<br />
## FILE_EPG
<!-- md:control env -->
<!-- md:version stable-1.1.0 -->
<!-- md:default `xmltv.xml` -->
The `FILE_EPG` environment variable specifies the filename that will be utilized when
your .xml EPG playlist file is generated.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- FILE_EPG=xmltv.xml # (1)
```
1. :information: Changing this file only changes the filename locally; it does
not affect the server-side fetching mechanism.
<br />
## FILE_GZIP
<!-- md:control env -->
<!-- md:version stable-1.1.0 -->
<!-- md:default `xmltv.xml.gz` -->
The `FILE_GZIP` environment variable specifies the filename that will be utilized when
a compressed `.gzip` is generated and when you download the file.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- FILE_GZIP=xmltv.xml.gz # (1)
```
1. :information: Changing this file only changes the filename locally for
generation and downloading. It does not affect the server-side fetching
mechanism.
<br />
## STREAM_QUALITY
<!-- md:control env -->
<!-- md:version stable-1.1.0 -->
<!-- md:default `hd` -->
The `STREAM_QUALITY` environment variable specifies the default stream quality that will
be used when you initiate a new channel to view.
Available Options:
* `hd` High Definition
* `sd` Standard Definition
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- STREAM_QUALITY=hd # (1)
```
1. :information: This environment variable has the following options:
- hd
- sd
<br />
## DIR_BUILD
<!-- md:control env -->
<!-- md:version stable-1.0.0 -->
<!-- md:default `/usr/src/app` -->
<!-- md:flag dangerous -->
The `DIR_BUILD` environment variable specifies what local folder will be utilized
by the TVApp2 docker container when the container builds the app.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- DIR_BUILD=/usr/src/app # (1)
```
1. :warning: You should not change this unless you are an advanced user.
<br />
## DIR_RUN
<!-- md:control env -->
<!-- md:version stable-1.0.0 -->
<!-- md:default `/usr/src/app` -->
<!-- md:flag dangerous -->
The `DIR_RUN` environment variable specifies what local folder will be utilized
by the TVApp2 docker container when the container has built the app and placed it
into a production folder.
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- DIR_RUN=/usr/bin/app # (1)
```
1. :warning: You should not change this unless you are an advanced user.
<br />
## LOG_LEVEL
<!-- md:control env -->
<!-- md:version stable-1.1.0 -->
<!-- md:default `4` -->
The `LOG_LEVEL` environment variable allows you specify how deep logs should go
when being output to your console.
=== "Example"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- LOG_LEVEL=4 # (1)
```
1. :information: The default log level is `4` (info).
=== "Log Levels"
| Log Level | Name | Description |
| --------------- | ----------- | ------------------------------------------------------------------------------- |
| `6` | :material-checkbox-blank-circle:{ style="color: var(--md-loglevel-trace-color) " } Trace | Displays all possible logs in console, along with anything below this line. |
| `5` | :material-checkbox-blank-circle:{ style="color: var(--md-loglevel-debug-color) " } Debug | Displays debug / developer logs, along with anything below this line. |
| `4` | :material-checkbox-blank-circle:{ style="color: var(--md-loglevel-info-color) " } Info | Displays informative logs, along with anything below this line. |
| `3` | :material-checkbox-blank-circle:{ style="color: var(--md-loglevel-notice-color) " } Notice | Displays important notices, along with anything below this line. |
| `2` | :material-checkbox-blank-circle:{ style="color: var(--md-loglevel-warn-color) " } Warn | Displays warnings, along with anything below this line. |
| `1` | :material-checkbox-blank-circle:{ style="color: var(--md-loglevel-error-color) " } Error | Displays only errors, none of the log levels above this line will be shown |
<br />
<br />

109
docs/docs/config/volumes.md Normal file
View File

@@ -0,0 +1,109 @@
---
title: Environment Variables
tags:
- config
---
# Mountable Volumes
Mountable volumes in Docker allow you to share folders within a docker container with your host machine. This allows you to access these specific files without having to bash into the container and using the terminal to navigate around.
The TVApp2 docker image provides a few different paths that you can mount to your host machine; as outlined below.
<br />
## 📁 /usr/bin/app
<!-- md:control volume -->
<!-- md:version stable-1.0.0 -->
The mountable volume `/usr/bin/app` is where TVApp2 files will be placed once the app has been built when your docker container spins up. The files in this folder include:
| File | Description |
| --- | --- |
| `📁 node_modules` | List of all NodeJS packages utilized by TVApp2 |
| `📁 www` | Main storage folder for TVApp2. Contains website files and M3U / EPG synced files |
| `📄 package.json` | NodeJS package file |
| `📄 playlist.m3u8` | Generated playlist containing channels |
| `📄 urls.txt` | List containing cached URLs utilized by TVApp2 |
| `📄 xmltv.xml` | EPG guide data in uncompressed XML format |
| `📄 xmltv.xml.gz` | EPG guide data in compressed GZ archive |
| `📄 index.js` | Main source code file for TVApp2 |
<br />
=== "Example"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="7" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
volumes:
- ./app:/usr/bin/app # (1)
```
1. :information: Changing this env variable will change the time for anything
related to the TVApp2 docker container.
<br />
This folder path can be changed by specifying a new path with the environment variable `DIR_RUN`
=== "Example"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="7" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
volumes:
- ./app:/usr/bin/app # (1) (2)
```
1. :information: Changing this env variable will change the folder within the docker container which stores the fully built TVApp2 files.
2. This should not be used unless you know what you're doing
<br />
---
<br />
## 📁 /config
<!-- md:control volume -->
<!-- md:version stable-1.0.0 -->
The mountable volume `/config` defines where the TVApp2 application will store SSL certificates related to the TVApp2 web interface being ran using https instead of http. The files in this folder include:
| File | Description |
| --- | --- |
| `📁 keys` | Folder which stores the SSL cert and keys |
| `📄 keys/cert.crt` | SSL public certificate |
| `📄 keys/key.crt` | SSL private key |
<br />
=== "Example"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="7" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
volumes:
- ./config:/config # (1) (2)
```
1. :information: Changing this env variable will change the folder within the docker container which stores the fully built TVApp2 files.
2. This should not be used unless you know what you're doing
<br />
<br />

View File

@@ -0,0 +1,13 @@
[mkdocs]: https://www.mkdocs.org
[mkdocs.dotfiles]: https://www.mkdocs.org/dev-guide/themes/#dot-files
[mkdocs.metadata]: https://www.mkdocs.org/user-guide/writing-your-docs/#yaml-style-meta-data
[mkdocs.env]: https://www.mkdocs.org/user-guide/configuration/#environment-variables
[mkdocs.docs_dir]: https://www.mkdocs.org/user-guide/configuration/#docs_dir
[mkdocs.extra_templates]: https://www.mkdocs.org/user-guide/configuration/#extra_templates
[mkdocs.site_dir]: https://www.mkdocs.org/user-guide/configuration/#site_dir
[mkdocs.site_url]: https://www.mkdocs.org/user-guide/configuration/#site_url
[mkdocs.site_description]: https://www.mkdocs.org/user-guide/configuration/#site_description
[mkdocs.nav]: https://www.mkdocs.org/user-guide/configuration/#nav
[mkdocs.plugins]: https://www.mkdocs.org/user-guide/configuration/#plugins
[mkdocs.strict]: https://www.mkdocs.org/user-guide/configuration/#strict
[mkdocs.use_directory_urls]: https://www.mkdocs.org/user-guide/configuration/#use_directory_urls

View File

@@ -0,0 +1,125 @@
---
title: "Install: docker-run"
tags:
- install
---
# docker run
Our documentation provides two ways that you may start up a TVApp2 docker container:
<div class="grid cards" markdown>
- :material-circle: &nbsp; [docker run](docker-run.md)
---
Spin up the TVApp2 container using the `docker run` command.
This is useful for quick launches, but is not time efficient
if you plan to use this container long-term.
This requires a longer command that must be used each time
you wish to bring the container up.
- :material-circle: &nbsp; [docker compose](docker-compose.md)
---
Spin up the TVApp2 container by creating a `📄 docker-compose.yml`
file which will store all of your options such as env variables,
mounted volumes, and labels.
To bring the container up, `cd` into the folder with the
`📄 docker-compose.yml` file, and run the
command `docker compose up -d`.
</div>
<br />
The `🗔 docker run` command allows you to start up a docker container by providing a set of [options](#command-options) which define how the container should operate, including the environment variables, mounted volumes, assigned IP address, etc.
<br />
---
<br />
## Start TVApp2
Pulling the image if needed and starting the container. To spin up a TVApp2 container using this method; run a command similar to the below example. See the section [Options](#options) below for a list of what you can specify.
=== "Terminal"
```shell
docker run -d \
--restart=unless-stopped \ # (1)!
--name tvapp2 \ # (2)!
-p 4124:4124 \ # (3)!
-e "TZ=Etc/UTC" \ # (4)!
-v ${PWD}/app:/usr/bin/app \ # (5)!
ghcr.io/thebinaryninja/tvapp2:latest # (6)!
```
1. Specifies what happens if the container becomes unresponsive or goes down.
2. Name to assign the container; otherwise, a random id will be given.
3. Port that will be used for the container
4. Environment variable which specifies the timezone to use for the container.
5. Mount the container volume `/usr/bin/app` to your host machine in the subfolder `./app`
6. Specifies what docker image to spin up.
<br />
To confirm that the container has been brought up, run the command `docker ps | grep tvapp2`. If you have the app [Portainer](https://portainer.io/), you can sign into your admin interface and view your TVApp2 container details, instead of using a command-line.
```
e95236c42b43 binaryninja/tvapp2:1.4.0 "/init" 3 seconds ago Up 3 seconds 4124/tcp tvapp2
```
<br />
---
<br />
## Options
Review the list of docker run options below. These allow you to define how a docker container will start up.
???- note "Official Docker Documentation"
To view a full list of the available docker parameters, view the official docker documentation at:
- https://docs.docker.com/reference/cli/docker/container/run/
| Parameter / Flag | Description |
| --- | --- |
| `-d, --detach` | Run container in background and print container ID |
| `-e, --env` | Set environment variable |
| `--env-file` | Read in a file of environment variables |
| `--expose` | Expose a port or a range of ports |
| `--health-cmd` | Command to run to check health |
| `--health-interval` | Time between running the check<br/>`ms|s|m|h` (default 0s) |
| `--health-retries` | Consecutive failures needed to report unhealthy |
| `--health-start-interval` | Time between running the check during the start period<br/>`ms|s|m|h` (default 0s) |
| `--health-start-period` | Start period for the container to initialize before starting health-retries countdown<br/>`ms|s|m|h` (default 0s) |
| `--health-timeout` | Maximum time to allow one check to run<br/>`ms|s|m|h` (default 0s) |
| `-h, --hostname` | Container host name |
| `--ip` | IPv4 address (e.g., 172.30.100.104) |
| `--ip6` | IPv6 address (e.g., 2001:db8::33) |
| `-l, --label` | Set meta data on a container |
| `--mount` | Attach a filesystem mount to the container |
| `--name` | Assign a name to the container |
| `--network` | Connect a container to a network |
| `--privileged` | Give extended privileges to this container |
| `-p, --publish` | Publish a container's port(s) to the host |
| `--pull` | Pull image before running<br/>`always`, `missing`, `never` |
| `--restart` | Restart policy to apply when a container exits <br/> `on-failure[:max-retries]`, `always`, `unless-stopped` |
| `-u, --user` | Username or UID <br/>`<name|uid>[:<group|gid>]` |
| `-v, --volume` | Bind mount a volume |
| `-w, --workdir` | Working directory inside the container |
<br />
<br />
<br />

View File

@@ -0,0 +1,47 @@
---
title: Install
tags:
- install
---
# Installing TVApp2
To install TVApp2 using docker; you will need to use either the `🗔 docker run` command, or create a `📄 docker-compose.yml` file which contains information about how to pull the latest image and spin the container up. We have provided instructions for both.
<br />
{==
Select your desired option to bring up the TVApp2 container with:
==}
<div class="grid cards" markdown>
- :material-circle: &nbsp; [docker run](docker-run.md)
---
Spin up the TVApp2 container using the `docker run` command.
This is useful for quick launches, but is not time efficient
if you plan to use this container long-term.
This requires a longer command that must be used each time
you wish to bring the container up.
- :material-circle: &nbsp; [docker compose](docker-compose.md)
---
Spin up the TVApp2 container by creating a `📄 docker-compose.yml`
file which will store all of your options such as env variables,
mounted volumes, and labels.
To bring the container up, `cd` into the folder with the
`📄 docker-compose.yml` file, and run the
command `docker compose up -d`.
</div>
<br />
<br />

View File

@@ -0,0 +1,6 @@
document$.subscribe(function() {
var tables = document.querySelectorAll("article table:not([class])")
tables.forEach(function(table) {
new Tablesort(table)
})
})

View File

@@ -5,6 +5,16 @@
[ data-md-color-scheme="default" ]
{
--md-accent-fg-color--transparent: #1b1b1b2f;
--md-typeset-a-color: #568afa;
--md-badge-icon-color: #2b2b2b;
--md-badge-icon-hover-color: #568afa;
--md-badge-bg-color--transparent: #1b1b1b2f;
--md-badge-bg-hover-color: #1b1b1b2f;
--md-table-head-bg-color: #597dd2;
--md-table-body-bg-color: rgb(221, 221, 221);
--md-table-body-text-color: rgb(20, 20, 20);
/*
Tag > Background Color
*/
@@ -24,15 +34,6 @@
line-height: 1.3;
}
/*
Badge > Left Box (Icon
*/
.md-typeset .mdx-badge__icon
{
background-color: var(--md-accent-fg-color);
}
/*
Badges > Right Box
*/
@@ -52,15 +53,6 @@
color: #FFF;
}
/*
Badge > Icon Color
*/
.md-typeset .mdx-badge svg
{
color: #FFFFFF;
}
/*
Make <small>text</small> opaque. used for changelog to show date
*/
@@ -84,16 +76,10 @@
margin-inline-end: 5px;
}
h3 .mdx-badge svg
{
padding-left: 2px;
padding-top: 1px;
}
.color-box {
float: left;
height: 24px;
width: 24px;
height: 21px;
width: 21px;
border: 1px solid rgba( 255, 255, 255, 0.2 );
clear: both;
}
@@ -111,11 +97,6 @@
padding-left: 4px;
}
.md-typeset table:not([class]) tbody tr
{
background-color: rgb(255, 255, 255);
}
/*
Table > Outter Border / Background
*/
@@ -125,6 +106,17 @@
}
.md-typeset table:not([class]) td
{
border-top: 2px solid var(--md-typeset-table-color);
padding: .9375em 1.25em;
padding-top: 0.9375em;
padding-bottom: 0.9375em;
padding-left: 1.25em;
vertical-align: top;
line-height: 20px;
}
/*
Table > Outter Border / Background
*/
@@ -156,15 +148,6 @@
{
white-space: nowrap;
/*background-color: var(--md-code-bg-color);*/
min-width: 25%;
border-right: 1px solid rgba( 255, 255, 255, 0.14);
background-color: rgba(194, 194, 194, 0.28);
}
.md-typeset__table thead
{
background-color: var(--md-accent-fg-color);
color: #FFF;
}
/*
@@ -196,6 +179,11 @@
}
}
[dir="ltr"] .md-typeset table th[role="columnheader"]::after
{
margin-left: 5px;
}
/*
Theme > Main
*/
@@ -208,6 +196,13 @@
--md-tooltip-width: 400px;
--glow-color: hsl(186 100% 69%);
--md-loglevel-error-color: #f06090;
--md-loglevel-warn-color: #e6695b;
--md-loglevel-notice-color: #e2d55e;
--md-loglevel-info-color: #6791e0;
--md-loglevel-debug-color: #c973d9;
--md-loglevel-trace-color: hsla(225deg,15%,90%,0.56);
--md-default-fg-color: hsla(var(--md-hue),15%,90%,0.82);
--md-default-fg-color--light: hsla(var(--md-hue),15%,90%,0.56);
--md-default-fg-color--lighter: hsla(var(--md-hue),15%,90%,0.32);
@@ -233,29 +228,42 @@
--md-code-hl-generic-color: var(--md-default-fg-color--light);
--md-code-hl-variable-color: var(--md-default-fg-color--light);
--md-typeset-color: var(--md-default-fg-color);
--md-typeset-a-color: #568afa;
--md-typeset-a-color: #f91d5b;
--md-badge-icon-color: #d0d0d0;
--md-badge-icon-hover-color: #568afa;
--md-badge-bg-color--transparent: #ffffff1c;
--md-badge-bg-hover-color: #FFFFFF2E;
--md-typeset-kbd-color: hsla(var(--md-hue),15%,90%,0.12);
--md-typeset-kbd-accent-color: hsla(var(--md-hue),15%,90%,0.2);
--md-typeset-kbd-border-color: hsla(var(--md-hue),15%,14%,1);
--md-typeset-mark-color: #4287ff4d;
--md-typeset-table-color: hsla(var(--md-hue),15%,95%,0.12);
--md-typeset-table-color: hsla(0%, 0%, 40%, 0.12);
--md-typeset-table-color--light: hsla(var(--md-hue),15%,95%,0.035);
--md-admonition-fg-color: var(--md-default-fg-color);
--md-admonition-bg-color: var(--md-default-bg-color);
--md-footer-bg-color: rgba( 34, 34, 34, 1 );
--md-footer-bg-color--dark: rgba( 15, 15, 15, 1 );
--md-footer-bg-color: rgba( 17, 17, 17, 1 );
--md-footer-bg-color--dark: rgba( 13, 13, 13, 1 );
--md-shadow-z1: 0 0.2rem 0.5rem #0000000d,0 0 0.05rem #0000001a;
--md-shadow-z2: 0 0.2rem 0.5rem #00000040,0 0 0.05rem #00000040;
--md-shadow-z3: 0 0.2rem 0.5rem #0006,0 0 0.05rem #00000059;
--md-table-head-bg-color: #860038;
--md-table-body-text-color: rgb(231, 231, 231);
--md-accent-fg-color--transparent: #FFFFFF2E;
--md-admonition-icon--pied-piper: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M244 246c-3.2-2-6.3-2.9-10.1-2.9-6.6 0-12.6 3.2-19.3 3.7l1.7 4.9zm135.9 197.9c-19 0-64.1 9.5-79.9 19.8l6.9 45.1c35.7 6.1 70.1 3.6 106-9.8-4.8-10-23.5-55.1-33-55.1zM340.8 177c6.6 2.8 11.5 9.2 22.7 22.1 2-1.4 7.5-5.2 7.5-8.6 0-4.9-11.8-13.2-13.2-23 11.2-5.7 25.2-6 37.6-8.9 68.1-16.4 116.3-52.9 146.8-116.7C548.3 29.3 554 16.1 554.6 2l-2 2.6c-28.4 50-33 63.2-81.3 100-31.9 24.4-69.2 40.2-106.6 54.6l-6.3-.3v-21.8c-19.6 1.6-19.7-14.6-31.6-23-18.7 20.6-31.6 40.8-58.9 51.1-12.7 4.8-19.6 10-25.9 21.8 34.9-16.4 91.2-13.5 98.8-10zM555.5 0l-.6 1.1-.3.9.6-.6zm-59.2 382.1c-33.9-56.9-75.3-118.4-150-115.5l-.3-6c-1.1-13.5 32.8 3.2 35.1-31l-14.4 7.2c-19.8-45.7-8.6-54.3-65.5-54.3-14.7 0-26.7 1.7-41.4 4.6 2.9 18.6 2.2 36.7-10.9 50.3l19.5 5.5c-1.7 3.2-2.9 6.3-2.9 9.8 0 21 42.8 2.9 42.8 33.6 0 18.4-36.8 60.1-54.9 60.1-8 0-53.7-50-53.4-60.1l.3-4.6 52.3-11.5c13-2.6 12.3-22.7-2.9-22.7-3.7 0-43.1 9.2-49.4 10.6-2-5.2-7.5-14.1-13.8-14.1-3.2 0-6.3 3.2-9.5 4-9.2 2.6-31 2.9-21.5 20.1L15.9 298.5c-5.5 1.1-8.9 6.3-8.9 11.8 0 6 5.5 10.9 11.5 10.9 8 0 131.3-28.4 147.4-32.2 2.6 3.2 4.6 6.3 7.8 8.6 20.1 14.4 59.8 85.9 76.4 85.9 24.1 0 58-22.4 71.3-41.9 3.2-4.3 6.9-7.5 12.4-6.9.6 13.8-31.6 34.2-33 43.7-1.4 10.2-1 35.2-.3 41.1 26.7 8.1 52-3.6 77.9-2.9 4.3-21 10.6-41.9 9.8-63.5l-.3-9.5c-1.4-34.2-10.9-38.5-34.8-58.6-1.1-1.1-2.6-2.6-3.7-4 2.2-1.4 1.1-1 4.6-1.7 88.5 0 56.3 183.6 111.5 229.9 33.1-15 72.5-27.9 103.5-47.2-29-25.6-52.6-45.7-72.7-79.9zm-196.2 46.1v27.2l11.8-3.4-2.9-23.8zm-68.7-150.4l24.1 61.2 21-13.8-31.3-50.9zm84.4 154.9l2 12.4c9-1.5 58.4-6.6 58.4-14.1 0-1.4-.6-3.2-.9-4.6-26.8 0-36.9 3.8-59.5 6.3z"/></svg>');
/*
Tag > Background Color
Navigation > Tabs
*/
.md-typeset .md-tag
.md-tabs
{
background-color: var(--md-primary-fg-color);
background-color: #860038;
color: var(--md-primary-bg-color);
display: block;
line-height: 1.3;
overflow: auto;
width: 100%;
z-index: 3;
}
/*
@@ -264,8 +272,8 @@
.md-nav
{
font-size: .6rem;
line-height: 1.3;
font-size: .7rem;
line-height: 1.4;
}
.mdx-container
@@ -276,12 +284,18 @@
.md-main
{
background-color: hsla(140, 0%, 5%, 1) !important;
background-color: #161618 !important;
}
.md-main__inner
{
display: flex;
height: 100%;
}
.md-typeset table:not([ class ] ) tbody tr
{
background-color: rgb( 17, 15, 15 );
background-color: rgb( 15, 15, 15 );
}
/*
@@ -315,14 +329,6 @@
{
white-space: nowrap;
/*background-color: var(--md-code-bg-color);*/
min-width: 25%;
border-right: 1px solid rgba( 255, 255, 255, 0.14);
background-color: rgba(70, 65, 77, 0.36);
}
.md-typeset__table thead
{
background-color: #b10f52;
}
/*
@@ -417,16 +423,28 @@
.md-typeset .tabbed-labels
{
background-color: #090909;
background-color: #0b0b0b;
border-top-left-radius: 4px;
border-left: 1px solid #242735;
border-top: 1px solid #242735;
border-right: 1px solid #242735;
border-top-right-radius: 4px;
}
/*
Badge > Left Box (Icon
*/
.md-typeset .mdx-badge__icon
.tabbed-labels::before
{
background-color: #3f4faa;
background: hsla(343.1, 87.9%, 51.6%, 0.82);
bottom: 0;
content: "";
display: block;
height: 2px;
left: 0;
position: absolute;
transform: translateX(var(--md-indicator-x));
transition: width 225ms,background-color .25s,transform .25s;
transition-timing-function: ease, ease, ease;
transition-timing-function: cubic-bezier(.4,0,.2,1);
width: var(--md-indicator-width);
}
/*
@@ -453,8 +471,7 @@
h3 .mdx-badge svg
{
padding-left: 2px;
padding-top: 1px;
padding-left: 0px;
}
/*
@@ -476,15 +493,6 @@
color: #FFF;
}
/*
Badge > Icon Color
*/
.md-typeset .mdx-badge svg
{
color: #FFFFFF;
}
}
/*
@@ -509,6 +517,14 @@ h4:has(.twemoji) .twemoji
margin-left: -7px;
}
.md-typeset .emojione, .md-typeset .gemoji, .md-typeset .twemoji
{
--md-icon-size: 1.125em;
display: inline-flex;
height: var(--md-icon-size);
vertical-align: middle;
}
/*
Badge > Header Padding
if a badge is used in a header, add padding between the badge and text
@@ -523,12 +539,18 @@ h4:has(.twemoji) .twemoji
*/
.md-nav--primary .md-nav__title
{
background: none;
box-shadow: none;
}
/*
Navigation > Spacer
Add a gap between the last "File" item in nav menu, and the folders under.
*/
li:has(a[href*="about_patcher"])
.md-sidebar--primary li:has(a[href*="about_tvapp2"])
{
margin-bottom: 28px !important;
}
@@ -563,6 +585,53 @@ li:has(a[href*="about_patcher"])
transition: none !important;
}
.md-typeset .mdx-badge--dangerous
{
--md-badge-icon-color: #fff;
--md-badge-icon-hover-color: #ff1058;
--md-badge-bg-color--transparent: #7B1532 !important;
color: #f9f9f9;
--md-typeset-bg-color: #111;
}
@keyframes health
{
0%, 40%, 80%, 100% {
transform: scale(1);
color: #FFF;
fill: #FFF !important;
}
20%, 60% {
transform: scale(1.15);
color: #FF6593;
fill: #FF6593 !important;
}
}
.heart
{
animation: health 1000ms infinite;
}
.md-typeset .mdx-badge--heart {
--md-badge-icon-color: #e92063;
--md-badge-icon-hover-color: #FFFFFF;
--md-accent-fg-color: #ff4281;
--md-accent-fg-color--transparent: #e920631a;
}
.md-typeset .mdx-badge--heart .twemoji {
animation: heart 1s infinite;
}
.md-typeset .mdx-badge--command
{
--md-typeset-a-color: #ff2d72;
--md-accent-fg-color: #fff;
--md-accent-fg-color--transparent: #ffffff1a;
color: #e91e63;
}
/*
Badge > Header Positioning
This is mainly for the conventions page
@@ -584,7 +653,6 @@ h2:has(.mdx-badge) > .mdx-badge svg
:is(h1, h2, h3, h4, h5, h6) .mdx-badge__icon svg
{
padding-top: 3px !important;
padding-right: 1px;
}
@@ -598,6 +666,22 @@ h2:has(.mdx-badge) > .mdx-badge svg
}
/*
Table > Header
*/
.md-typeset__table table thead, .md-typeset__table > table
{
background-color: var(--md-table-head-bg-color);
color: #FFF;
}
.md-typeset table:not([class]) tbody tr
{
background-color: var(--md-table-body-bg-color);
color: var(--md-table-body-text-color);
}
.mdx-badge__text a
{
padding-top: 2px;
@@ -614,20 +698,40 @@ small
font-size: 60%;
}
/*
Navigation > Category Gap
if you have multiple sub-folders for a group of pages, add a gap between categories on left-side navigation
*/
.md-nav__item--section
{
display: block;
margin: 2.00em 0;
margin-bottom: 2em;
}
/*
Menu Navigation > FontAwesome Folder Icons (for folders)
*/
label.md-nav__link
{
color: #ffffff59 !important;
}
label.md-nav__link::before
{
content: ".";
opacity: 0.6;
content: "";
background-color: currentColor;
padding-right: 0.6rem;
margin-right: 0.1rem;
mask-repeat: no-repeat;
mask-size: contain;
mask-size: 20px;
padding-inline: 4px;
mask-position: center;
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 576 512' fill='currentColor' width='16px' height='16px'%3E%3C!-- --%3E%3Cdefs%3E%3Cstyle%3E.fa-secondary%7Bopacity:.4%7D%3C/style%3E%3C/defs%3E%3Cpath class='fa-primary' d='M160 384H512c35.3 0 64-28.7 64-64V160c0-35.3-28.7-64-64-64H394.5c-17 0-33.3-6.7-45.3-18.7L322.7 50.7c-12-12-28.3-18.7-45.3-18.7H160c-35.3 0-64 28.7-64 64V320c0 35.3 28.7 64 64 64z'%3E%3C/path%3E%3Cpath class='fa-secondary' d='M24 96c13.3 0 24 10.7 24 24V344c0 48.6 39.4 88 88 88H456c13.3 0 24 10.7 24 24s-10.7 24-24 24H136C60.9 480 0 419.1 0 344V120c0-13.3 10.7-24 24-24z'%3E%3C/path%3E%3C/svg%3E");
opacity: 0.6;
}
li.md-nav__item--nested > a > span::before
@@ -652,10 +756,56 @@ nav.md-nav > ul.md-nav__list > li.md-nav__item > a.md-nav__link > span::before
padding-right: 0.6rem;
margin-right: 0.1rem;
mask-repeat: no-repeat;
mask-size: contain;
mask-size: 9px;
padding-inline: 4px;
mask-position: center;
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512' fill='currentColor' width='16px' height='16px'%3E%3C!-- --%3E%3Cdefs%3E%3Cstyle%3E.fa-secondary%7Bopacity:.4%7D%3C/style%3E%3C/defs%3E%3Cpath class='fa-primary' d=''%3E%3C/path%3E%3Cpath class='fa-secondary' d='M0 256a160 160 0 1 1 320 0A160 160 0 1 1 0 256z'%3E%3C/path%3E%3C/svg%3E");
}
/*
Menu Navigation > Secondary > Text Size
*/
nav.md-nav--secondary li
{
font-size: 0.6rem !important;
}
/*
Badge > Override Icon Color
*/
.mdx-badge a
{
color: var(--md-badge-icon-color);
word-break: break-word;
}
.mdx-badge a:hover
{
color: var(--md-badge-icon-hover-color);
word-break: break-word;
}
.mdx-badge .mdx-badge__icon
{
background-color: var(--md-badge-bg-color--transparent);
}
.mdx-badge .mdx-badge__icon:hover
{
background-color: var(--md-badge-bg-hover-color);
}
/*
Tag > Background Color
*/
.md-typeset .md-tag
{
background-color: var(--md-primary-fg-color);
}
/*
Lightbox border
*/
@@ -727,6 +877,12 @@ figure img
transition: border .25s,box-shadow .25s;
border: 2px solid rgba( 255, 255, 255, 0.1 );
background-color: var(--md-default-fg-color--lightest);
margin-top: 10px;
margin-bottom: 10px;
margin: 0 auto;
margin-bottom: 0px;
margin-top: 5px;
width: clamp(200px, 100%, 75%);
}
.mkde-embed-container:hover
@@ -815,14 +971,14 @@ figure img
.mkde-right
{
padding: 8px 10px;
padding: 0px 10px;
}
@media ( min-width: 360px )
{
.mkde-right
{
padding: 12px 15px;
padding: 0px 15px;
}
}
@@ -830,7 +986,7 @@ figure img
{
.mkde-right
{
padding: 12px 12px;
padding: 0px 12px;
}
}
@@ -840,10 +996,12 @@ figure img
._lc._sm:not( .xd ) .mkde-left
{
min-width: 100px;
width: 100px;
min-height: 100px;
padding: 10px;
min-width: 70px;
width: 70px;
min-height: 70px;
height: 70px;
padding: 5px;
align-self: center;
background: rgba( 255, 255, 255, 0.10 );
}
@@ -851,9 +1009,11 @@ figure img
{
._lc._sm:not( .xd ) .mkde-left
{
min-width: 110px;
width: 110px;
min-height: 110px;
min-width: 80px;
width: 80px;
min-height: 80px;
height: 80px;
align-self: center;
}
}
@@ -861,9 +1021,11 @@ figure img
{
._lc._sm:not( .xd ) .mkde-left
{
min-width: 140px;
width: 140px;
min-height: 140px;
min-width: 110px;
width: 110px;
min-height: 110px;
height: 110px;
align-self: center;
}
}
@@ -871,9 +1033,11 @@ figure img
{
._lc._sm:not( .xd ) .mkde-left
{
min-width: 160px;
width: 160px;
min-height: 160px;
min-width: 110px;
width: 110px;
min-height: 110px;
height: 110px;
align-self: center;
}
}
@@ -1049,24 +1213,24 @@ figure img
._fs-x,
._fs-m
{
font-size: 12px;
font-size: clamp(7px, 2.0vw, 12px);
}
._f1p
{
font-size: 13px;
font-size: clamp(10px, 2.5vw, 13px);
}
@media ( min-width: 360px )
{
._fs-x
{
font-size: 13px;
font-size: clamp(10px, 2.5vw, 13px);
}
._f1p
{
font-size: 14px;
font-size: clamp(10px, 2.5vw, 14px);
}
}
@@ -1074,17 +1238,17 @@ figure img
{
._fs-m
{
font-size: 13px;
font-size: clamp(10px, 2.5vw, 13px);
}
._fs-x
{
font-size: 14px;
font-size: clamp(10px, 2.5vw, 14px);
}
._f1p
{
font-size: 15px;
font-size: clamp(10px, 2.5vw, 15px);
}
}
@@ -1092,17 +1256,17 @@ figure img
{
._fs-m
{
font-size: 14px;
font-size: clamp(10px, 2.5vw, 13px);
}
._fs-x
{
font-size: 15px;
font-size: clamp(10px, 2.5vw, 14px);
}
._f1p
{
font-size: 17px;
font-size: clamp(10px, 2.5vw, 15px);
}
}
@@ -1216,13 +1380,12 @@ figure img
.item-link
{
color: var( --md-typeset-a-color );
font-size: 12px;
font-size: clamp(8px, 2.5vw, 12px);
padding-bottom: 6px;
}
.item-footer
{
padding-block-start: 12px;
padding-left: 5px;
}
@@ -1255,14 +1418,14 @@ figure img
{
display: inline-block;
padding-left: 5px;
margin-block-end: -5px;
margin-block-end: -7px;
}
.color-box
{
float: left;
height: 20px;
width: 20px;
height: 21px;
width: 21px;
border: 1px solid rgba( 255, 255, 255, 0.2 );
clear: both;
}
@@ -1276,3 +1439,187 @@ figure img
clear: both;
margin-right: 10px;
}
/*
Tools > Icon Search
*/
.md-typeset .mdx-iconsearch {
background-color:var(--md-default-bg-color);
border-radius:.1rem;
box-shadow:var(--md-shadow-z1);
position:relative;
transition:box-shadow 125ms
}
.md-typeset .mdx-iconsearch:focus-within,
.md-typeset .mdx-iconsearch:hover {
box-shadow:var(--md-shadow-z2)
}
.md-typeset .mdx-iconsearch .md-input {
background:var(--md-default-bg-color);
box-shadow:none
}
[data-md-color-scheme=slate] .md-typeset .mdx-iconsearch .md-input {
background:var(--md-code-bg-color)
}
.md-typeset .mdx-iconsearch-result {
-webkit-backface-visibility:hidden;
backface-visibility:hidden;
max-height:50vh;
overflow-y:auto;
scrollbar-color:var(--md-default-fg-color--lighter) #0000;
scrollbar-width:thin;
touch-action:pan-y
}
.md-tooltip .md-typeset .mdx-iconsearch-result {
max-height:10.25rem
}
.md-typeset .mdx-iconsearch-result::-webkit-scrollbar {
height:.2rem;
width:.2rem
}
.md-typeset .mdx-iconsearch-result::-webkit-scrollbar-thumb {
background-color:var(--md-default-fg-color--lighter)
}
.md-typeset .mdx-iconsearch-result::-webkit-scrollbar-thumb:hover {
background-color:var(--md-accent-fg-color)
}
.md-typeset .mdx-iconsearch-result__meta {
color:var(--md-default-fg-color--lighter);
font-size:.64rem;
position:absolute;
right:.6rem;
top:.4rem
}
@media screen and (max-width:29.984375em) {
.md-typeset .mdx-iconsearch-result__meta {
display:none
}
}
.md-typeset .mdx-iconsearch-result__select {
background-color:var(--md-default-fg-color--lightest);
border:none;
border-radius:.1rem;
color:var(--md-default-fg-color--light);
font-size:.64rem;
padding-bottom:.15em;
padding-top:.15em;
position:absolute;
right:.6rem;
top:.4rem;
transition:color 125ms,background-color 125ms
}
.md-typeset .mdx-iconsearch-result__select:focus,
.md-typeset .mdx-iconsearch-result__select:hover {
background-color:var(--md-accent-fg-color);
color:var(--md-accent-bg-color);
outline:none
}
.md-typeset .mdx-iconsearch-result__select+.mdx-iconsearch-result__meta {
right:4.1rem
}
[dir=ltr] .md-typeset .mdx-iconsearch-result__list {
margin-left:0
}
[dir=rtl] .md-typeset .mdx-iconsearch-result__list {
margin-right:0
}
.md-typeset .mdx-iconsearch-result__list {
list-style:none;
margin:0;
padding:0
}
[dir=ltr] .md-typeset .mdx-iconsearch-result__item {
margin-left:0
}
[dir=rtl] .md-typeset .mdx-iconsearch-result__item {
margin-right:0
}
.md-typeset .mdx-iconsearch-result__item {
border-bottom:.05rem solid var(--md-default-fg-color--lightest);
margin:0;
padding:.2rem .6rem
}
.md-typeset .mdx-iconsearch-result__item:last-child {
border-bottom:none
}
.md-typeset .mdx-iconsearch-result__item>* {
margin-right:.6rem
}
.md-typeset .mdx-iconsearch-result__item img {
height:.9rem;
width:.9rem
}
[data-md-color-scheme=slate] .md-typeset .mdx-iconsearch-result__item img[src*=squidfunk] {
filter:invert(1)
}
.md-typeset .mdx-premium p {
margin:2em 0;
text-align:center
}
.md-typeset .mdx-premium img {
height:3.25rem
}
.md-typeset .mdx-premium p:last-child {
display:flex;
flex-wrap:wrap;
justify-content:center
}
.md-typeset .mdx-premium p:last-child>a {
display:block;
flex-shrink:0
}
.md-typeset .mdx-sponsorship__list {
margin:2em 0
}
.md-typeset .mdx-sponsorship__list:after {
clear:both;
content:"";
display:block
}
[dir=ltr] .md-typeset .mdx-sponsorship__item {
float:left
}
[dir=rtl] .md-typeset .mdx-sponsorship__item {
float:right
}
.md-typeset .mdx-sponsorship__item {
border-radius:100%;
display:block;
height:1.6rem;
margin:.2rem;
overflow:hidden;
transform:scale(1);
transition:color 125ms,transform 125ms;
width:1.6rem
}
.md-typeset .mdx-sponsorship__item:focus,
.md-typeset .mdx-sponsorship__item:hover {
transform:scale(1.1)
}
.md-typeset .mdx-sponsorship__item:focus img,
.md-typeset .mdx-sponsorship__item:hover img {
filter:grayscale(0)
}
.md-typeset .mdx-sponsorship__item--private {
background:var(--md-default-fg-color--lightest);
color:var(--md-default-fg-color--lighter);
font-size:.6rem;
font-weight:700;
line-height:1.6rem;
text-align:center
}
.md-typeset .mdx-sponsorship__item img {
display:block;
filter:grayscale(100%) opacity(75%);
height:auto;
transition:filter 125ms;
width:100%
}
.md-typeset .mdx-sponsorship-button {
font-weight:400
}
.md-typeset .mdx-sponsorship-count,
.md-typeset .mdx-sponsorship-total {
font-weight:700
}

View File

@@ -0,0 +1,164 @@
---
title: Healthcheck
tags:
- usage
---
# Healthcheck
This docker container includes the ability to run health checks between the container api and outside services such as Portainer, or any other app that has the ability to query the endpoint `api/health`.
<br />
---
<br />
## API
<!-- md:version stable-1.4.0 -->
This container includes the ability to run health checks utilizing the built-in API. You can directly access the health check and view a json formatted result with the url `https://tvapp2.domain.lan/api/health`
=== "Example"
``` { .yaml .copy .select title="/api/health" linenums="1" }
{
"ip": "172.18.2.1",
"gateway": "172.18.0.1",
"uptime": 5903.549082501,
"message": "healthy",
"status": "healthy",
"ref": "/api/health",
"method": "GET",
"code": 200,
"timestamp": 1744386346242
}
```
<br />
Numerous aliases have been added; you can use any of the following URLs to access the same health check results as they go to the same endpoint:
- https://tvapp2.domain.lan/api/health
- https://tvapp2.domain.lan/api/status
<br />
---
<br />
## Portainer
<!-- md:version stable-1.3.0 -->
To run a health check between TVApp2 and Portainer, apply the following lines of code to your TVApp2 `docker-compose.yml`. Two examples have been provided, you can use either one; `wget` or `curl`:
=== "Using CURL"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="14-18" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- TZ=Etc/UTC
health check:
test: "curl --fail --silent http://127.0.0.1:4124/api/health | grep -i healthy || exit 1"
interval: 15s
timeout: 10s
retries: 3
```
=== "Using WGET"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="14-18" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- TZ=Etc/UTC
health check:
test: "wget -qO- http://127.0.0.1:4124/api/health | grep -i healthy || exit 1"
interval: 15s
timeout: 10s
retries: 3
```
<br />
---
<br />
## Web Interface
<!-- md:version stable-1.4.0 -->
The TVApp2 web interface is equip with its own health check which is ran every `10 minutes` by default. Users will notice a health indicator in the top right of the header navigation bar which is represented by the :octicons-heart-fill-24:{ .heart } icon.
When the health check is ran every 10 minutes; a toast notification will appear in the lower-right side of the page:
<figure markdown="span">
![Image settings](../../assets/images/health-toast.gif){ width="80%" }
<figcaption>Health check toast notification</figcaption>
</figure>
<br />
### Health Check Duration
<!-- md:control env -->
<!-- md:version stable-1.4.0 -->
<!-- md:default `600000` -->
By default, a health check between the TVApp2 container and the web interface is done every `10 minutes`, but you can change this duration to something less or more. Be aware that if you set the time too low, you will constantly be spammed with toast notifications.
To change the health check delay, add the environment variable `HEALTH_TIMER` to your TVApp2 `docker-compose.yml`. Duration is in milliseconds.
<br />
=== "Example"
``` { .yaml .copy .select title="docker-compose.yml" linenums="1" hl_lines="13" }
services:
tvapp2:
container_name: tvapp2
image: ghcr.io/thebinaryninja/tvapp2:latest
restart: unless-stopped
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
environment:
- HEALTH_TIMER=600000 # (1)
```
1. :information: Defines how often to perform health checks between the container and the web interface. Time is in milliseconds. <br /><br />Default value is `600000` (10 minutes)
=== "Time Chart"
| HEALTH_TIMER Value | Delay Between Checks |
| --- | --- |
| `HEALTH_TIMER=300000` | 5 minutes |
| `HEALTH_TIMER=600000` | 10 minutes |
| `HEALTH_TIMER=1200000` | 20 minutes |
| `HEALTH_TIMER=1800000` | 30 minutes |
| `HEALTH_TIMER=3600000` | 1 hour |
<br />
<br />

BIN
docs/img/core/01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
docs/img/core/02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
docs/img/core/03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
docs/img/core/04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/img/screenshots/01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M0 64L0 448c0 35.3 28.7 64 64 64l320 0c35.3 0 64-28.7 64-64l0-384c0-35.3-28.7-64-64-64L64 0C28.7 0 0 28.7 0 64zm64 48c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zm0 128c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zm0 128c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zM192 128c0-8.8 7.2-16 16-16l160 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-160 0c-8.8 0-16-7.2-16-16zm0 128c0-8.8 7.2-16 16-16l160 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-160 0c-8.8 0-16-7.2-16-16zm0 128c0-8.8 7.2-16 16-16l160 0c8.8 0 16 7.2 16 16s-7.2 16-16 16l-160 0c-8.8 0-16-7.2-16-16z"/><path class="fa-primary" d="M64 112c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zm0 128c0-8.8 7.2-16 16-16l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32zM80 352l32 0c8.8 0 16 7.2 16 16l0 32c0 8.8-7.2 16-16 16l-32 0c-8.8 0-16-7.2-16-16l0-32c0-8.8 7.2-16 16-16z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M264.8 64C277.6 64 288 74.4 288 87.2c0 9.2-5.8 17.3-13.2 22.8c-11.6 8.7-18.8 20.7-18.8 34c0 26.5 28.7 48 64 48s64-21.5 64-48c0-13.3-7.2-25.3-18.8-34c-7.4-5.5-13.2-13.6-13.2-22.8C352 74.4 362.4 64 375.2 64L464 64c26.5 0 48 21.5 48 48l0 88.8c0 12.8 10.4 23.2 23.2 23.2c9.2 0 17.3-5.8 22.8-13.2c8.7-11.6 20.7-18.8 34-18.8c26.5 0 48 28.7 48 64s-21.5 64-48 64c-13.3 0-25.3-7.2-34-18.8c-5.5-7.4-13.6-13.2-22.8-13.2c-12.8 0-23.2 10.4-23.2 23.2l0 88.8c0 26.5-21.5 48-48 48l-88.8 0c-12.8 0-23.2-10.4-23.2-23.2c0-9.2 5.8-17.3 13.2-22.8c11.6-8.7 18.8-20.7 18.8-34c0-26.5-28.7-48-64-48s-64 21.5-64 48c0 13.3 7.2 25.3 18.8 34c7.4 5.5 13.2 13.6 13.2 22.8c0 12.8-10.4 23.2-23.2 23.2L176 448c-26.5 0-48-21.5-48-48l0-88.8c0-12.8-10.4-23.2-23.2-23.2c-9.2 0-17.3 5.8-22.8 13.2C73.3 312.8 61.3 320 48 320c-26.5 0-48-28.7-48-64s21.5-64 48-64c13.3 0 25.3 7.2 34 18.8c5.5 7.4 13.6 13.2 22.8 13.2c12.8 0 23.2-10.4 23.2-23.2l0-88.8c0-26.5 21.5-48 48-48l88.8 0z"/><path class="fa-primary" d=""/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M192 104.8c0-9.2-5.8-17.3-13.2-22.8C167.2 73.3 160 61.3 160 48c0-26.5 28.7-48 64-48s64 21.5 64 48c0 13.3-7.2 25.3-18.8 34c-7.4 5.5-13.2 13.6-13.2 22.8c0 12.8 10.4 23.2 23.2 23.2l56.8 0c26.5 0 48 21.5 48 48l0 56.8c0 12.8 10.4 23.2 23.2 23.2c9.2 0 17.3-5.8 22.8-13.2c8.7-11.6 20.7-18.8 34-18.8c26.5 0 48 28.7 48 64s-21.5 64-48 64c-13.3 0-25.3-7.2-34-18.8c-5.5-7.4-13.6-13.2-22.8-13.2c-12.8 0-23.2 10.4-23.2 23.2L384 464c0 26.5-21.5 48-48 48l-56.8 0c-12.8 0-23.2-10.4-23.2-23.2c0-9.2 5.8-17.3 13.2-22.8c11.6-8.7 18.8-20.7 18.8-34c0-26.5-28.7-48-64-48s-64 21.5-64 48c0 13.3 7.2 25.3 18.8 34c7.4 5.5 13.2 13.6 13.2 22.8c0 12.8-10.4 23.2-23.2 23.2L48 512c-26.5 0-48-21.5-48-48L0 343.2C0 330.4 10.4 320 23.2 320c9.2 0 17.3 5.8 22.8 13.2C54.7 344.8 66.7 352 80 352c26.5 0 48-28.7 48-64s-21.5-64-48-64c-13.3 0-25.3 7.2-34 18.8C40.5 250.2 32.4 256 23.2 256C10.4 256 0 245.6 0 232.8L0 176c0-26.5 21.5-48 48-48l120.8 0c12.8 0 23.2-10.4 23.2-23.2z"/><path class="fa-primary" d=""/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M0 288L0 448c0 35.3 28.7 64 64 64l160 0 0-64c0-8.8-7.2-16-16-16l-16 0c-17.7 0-32-14.3-32-32s14.3-32 32-32l16 0c8.8 0 16-7.2 16-16l0-64-64 0c-8.8 0-16-7.2-16-16l0-16c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 16c0 8.8-7.2 16-16 16L0 288zM304 16l0 48c0 8.8 7.2 16 16 16l16 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-16 0c-8.8 0-16 7.2-16 16l0 32c0 8.8 7.2 16 16 16l36 0c6.6 0 12 5.4 12 12l0 4c0 17.7 14.3 32 32 32s32-14.3 32-32l0-4c0-6.6 5.4-12 12-12l52 0c8.8 0 16-7.2 16-16l0-128c0-35.3-28.7-64-64-64L320 0c-8.8 0-16 7.2-16 16z"/><path class="fa-primary" d="M0 288l64 0c8.8 0 16-7.2 16-16l0-16c0-17.7 14.3-32 32-32s32 14.3 32 32l0 16c0 8.8 7.2 16 16 16l64 0 0-68c0-6.6 5.4-12 12-12l4 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-4 0c-6.6 0-12-5.4-12-12l0-52c0-8.8-7.2-16-16-16L64 64C28.7 64 0 92.7 0 128L0 288zm224 0l0 64c0 8.8-7.2 16-16 16l-16 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l16 0c8.8 0 16 7.2 16 16l0 64 160 0c35.3 0 64-28.7 64-64l0-144c0-8.8-7.2-16-16-16l-48 0c-8.8 0-16 7.2-16 16l0 16c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-16c0-8.8-7.2-16-16-16l-64 0z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M3.4 273.7c-7.9 15.8-1.5 35 14.3 42.9L152.4 384 17.7 451.4C1.9 459.3-4.5 478.5 3.4 494.3C9 505.5 20.3 512 32 512c4.8 0 9.7-1.1 14.3-3.4L224 419.8l177.7 88.8c4.6 2.3 9.5 3.4 14.3 3.4c11.7 0 23-6.5 28.6-17.7c7.9-15.8 1.5-35-14.3-42.9L295.6 384l134.8-67.4c15.8-7.9 22.2-27.1 14.3-42.9C439 262.5 427.7 256 416 256c-4.8 0-9.7 1.1-14.3 3.4L224 348.2 46.3 259.4c-4.6-2.3-9.5-3.4-14.3-3.4c-11.7 0-23 6.5-28.6 17.7z"/><path class="fa-primary" d="M304 234.4c38.6-23 64-62.1 64-106.4C368 57.3 303.5 0 224 0S80 57.3 80 128c0 44.4 25.4 83.5 64 106.4l0 21.6c0 17.7 14.3 32 32 32l96 0c17.7 0 32-14.3 32-32l0-21.6zM136 144a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm144-32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>

After

Width:  |  Height:  |  Size: 1006 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M0 64C0 28.7 28.7 0 64 0L576 0c35.3 0 64 28.7 64 64l0 288c0 35.3-28.7 64-64 64L64 416c-35.3 0-64-28.7-64-64L0 64zm64 0l0 288 512 0 0-288L64 64zM96 480c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-384 0c-17.7 0-32-14.3-32-32z"/><path class="fa-primary" d="M64 64H576V352H64V64z"/></svg>

After

Width:  |  Height:  |  Size: 617 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M0 96C0 60.7 28.7 32 64 32l384 0c35.3 0 64 28.7 64 64l0 224c0 5.5-.7 10.8-2 16c-3.6-13.8-11.6-25.8-22.5-34.4c-5.4-4.3-11.6-7.7-18.2-10c-3.3-1.2-6.8-2.1-10.3-2.7c-1.8-.3-3.6-.5-5.4-.7c-.9-.1-1.8-.1-2.8-.2s-1.8-.1-2.8-.1L64 288c-29.8 0-54.9 20.4-62 48c-1.3-5.1-2-10.5-2-16L0 96zM352 384a32 32 0 1 1 -64 0 32 32 0 1 1 64 0z"/><path class="fa-primary" d="M0 352c0-35.3 28.7-64 64-64l384 0c35.3 0 64 28.7 64 64l0 64c0 35.3-28.7 64-64 64L64 480c-35.3 0-64-28.7-64-64l0-64zm352 32a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm64 32a32 32 0 1 0 0-64 32 32 0 1 0 0 64z"/></svg>

After

Width:  |  Height:  |  Size: 872 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM232 120c.1-13.4 10.8-24 24-24c6.6 0 12.6 2.7 17 7c2.2 2.2 3.9 4.8 5.1 7.6c.6 1.4 1.1 2.9 1.4 4.5c.2 .8 .3 1.6 .4 2.4s.1 1.6 .1 2.5c0 41 0 82.1 0 123.2L365.3 300c6.9 4.6 10.7 12.2 10.7 20c0 4.6-1.3 9.2-4 13.3c-4.6 6.9-12.2 10.7-20 10.7c-4.6 0-9.2-1.3-13.3-4c-32-21.3-64-42.7-96-64C236 271.5 232 264 232 256c0-45.3 0-90.7 0-136z"/><path class="fa-primary" d="M256 96c-13.3 0-24 10.7-24 24l0 136c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2 280 120c0-13.3-10.7-24-24-24z"/></svg>

After

Width:  |  Height:  |  Size: 870 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M225.2 471.2c-4.9 17 5 34.7 22 39.6c2.9 .8 5.9 1.2 8.8 1.2c13.9 0 26.7-9.2 30.7-23.2l128-448c.8-2.9 1.2-5.9 1.2-8.8c0-13.9-9.2-26.7-23.2-30.8c-17-4.9-34.7 5-39.6 22l-128 448z"/><path class="fa-primary" d="M473.4 121.4c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"/></svg>

After

Width:  |  Height:  |  Size: 879 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.7.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc. --><defs><style>.fa-secondary{opacity:.4}</style></defs><path class="fa-secondary" d="M192 104.8c0-9.2-5.8-17.3-13.2-22.8C167.2 73.3 160 61.3 160 48c0-26.5 28.7-48 64-48s64 21.5 64 48c0 13.3-7.2 25.3-18.8 34c-7.4 5.5-13.2 13.6-13.2 22.8c0 12.8 10.4 23.2 23.2 23.2l56.8 0c26.5 0 48 21.5 48 48l0 56.8c0 12.8 10.4 23.2 23.2 23.2c9.2 0 17.3-5.8 22.8-13.2c8.7-11.6 20.7-18.8 34-18.8c26.5 0 48 28.7 48 64s-21.5 64-48 64c-13.3 0-25.3-7.2-34-18.8c-5.5-7.4-13.6-13.2-22.8-13.2c-12.8 0-23.2 10.4-23.2 23.2L384 464c0 26.5-21.5 48-48 48l-56.8 0c-12.8 0-23.2-10.4-23.2-23.2c0-9.2 5.8-17.3 13.2-22.8c11.6-8.7 18.8-20.7 18.8-34c0-26.5-28.7-48-64-48s-64 21.5-64 48c0 13.3 7.2 25.3 18.8 34c7.4 5.5 13.2 13.6 13.2 22.8c0 12.8-10.4 23.2-23.2 23.2L48 512c-26.5 0-48-21.5-48-48L0 343.2C0 330.4 10.4 320 23.2 320c9.2 0 17.3 5.8 22.8 13.2C54.7 344.8 66.7 352 80 352c26.5 0 48-28.7 48-64s-21.5-64-48-64c-13.3 0-25.3 7.2-34 18.8C40.5 250.2 32.4 256 23.2 256C10.4 256 0 245.6 0 232.8L0 176c0-26.5 21.5-48 48-48l120.8 0c12.8 0 23.2-10.4 23.2-23.2z"/><path class="fa-primary" d=""/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

File diff suppressed because one or more lines are too long

View File

@@ -71,9 +71,9 @@ class clr():
MAGENTA = '\033[35m'
CYAN = '\033[36m'
WHITE = '\033[37m'
GREY = '\033[90m'
UNDERLINE = '\033[4m'
RESET = '\033[0m'
GREY = '\033[90m'
# #
# Pages
@@ -87,6 +87,8 @@ PAGE_CONVENTIONS = "about/conventions.md"
# #
# Hooks > on_page_markdown
#
# do not change this function name
# #
def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files: Files):
@@ -99,28 +101,28 @@ def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files:
args = args.strip()
if type == "version":
if args.startswith( "beta-" ):
return Version_Beta(args, page, files)
if args.startswith( "development-" ):
return Version_Development(args, page, files)
elif args.startswith( "stable-" ):
return Version_Stable( args, page, files )
else:
return Version( args, page, files )
elif type == "backers": return Badge_Backers(page, files)
elif type == "control": return Create_Control(args, page, files)
elif type == "flag": return Create_Flag(args, page, files)
elif type == "option": return Create_Option(args)
elif type == "setting": return Create_Setting(args)
elif type == "command": return Badge_Command(args, page, files)
elif type == "feature": return Badge_Feature(args, page, files)
elif type == "plugin": return Badge_Plugin(args, page, files)
elif type == "extension": return Badge_Extension(args, page, files)
elif type == "3rdparty": return Badge_3rdparty(args, page, files)
elif type == "example": return Badge_Example(args, page, files)
elif type == "control": return badgeControl(args, page, files)
elif type == "flag": return badgeFlag(args, page, files)
elif type == "option": return badgeOption(args)
elif type == "setting": return badgeSetting(args)
elif type == "backers": return badgeBackers(page, files)
elif type == "command": return badgeCommand(args, page, files)
elif type == "feature": return badgeFeature(args, page, files)
elif type == "plugin": return badgePlugin(args, page, files)
elif type == "markdown": return badgeMarkdown(args, page, files)
elif type == "3rdparty": return badge3rdParty(args, page, files)
elif type == "example": return badgeExample(args, page, files)
elif type == "default":
if args == "none": return Badge_DefaultValue_None(page, files)
elif args == "computed": return Badge_DefaultValue_Computed(page, files)
else: return Badge_DefaultValue_Custom(args, page, files)
if args == "none": return badgeDefaultNone(page, files)
elif args == "computed": return badgeDefaultVal(page, files)
else: return badgeDefaultCustom(args, page, files)
# Otherwise, raise an error
raise RuntimeError( f"Error in shortcodes.yp - Specified an unknown shortcode: {type}" )
@@ -135,31 +137,36 @@ def on_page_markdown(markdown: str, *, page: Page, config: MkDocsConfig, files:
# Create > Flag
# #
def Create_Flag(args: str, page: Page, files: Files):
def badgeFlag(args: str, page: Page, files: Files):
type, *_ = args.split(" ", 1)
if type == "experimental": return Badge_Flag_Experimental(page, files)
elif type == "required": return Badge_Flag_Required(page, files)
elif type == "customization": return Badge_Flag_Customization(page, files)
elif type == "metadata": return Badge_Flag_Metadata(page, files)
elif type == "multiple": return Badge_Flag_Multiple_Instances(page, files)
elif type == "setting": return Badge_Flag_Setting(page, files)
if type == "experimental": return badgeFlagExperimental(page, files)
elif type == "required": return badgeFlagRequired(page, files)
elif type == "customization": return badgeFlagCustomization(page, files)
elif type == "metadata": return badgeFlagMetadata(page, files)
elif type == "dangerous": return badgeFlagDangerous(page, files)
elif type == "multiple": return badgeFlagMultiInstances(page, files)
elif type == "setting": return badgeFlagSetting(page, files)
else: return badgeFlagDefault( page, files )
raise RuntimeError(f"Unknown type: {type}")
# #
# Create > Controls
# #
def Create_Control( args: str, page: Page, files: Files ):
def badgeControl( args: str, page: Page, files: Files ):
type, *_ = args.split( " ", 2 )
if type == "toggle": return icon_control_toggle( page, files )
elif type == "toggle_on": return icon_control_toggle_on( page, files )
elif type == "toggle_off": return icon_control_toggle_off( page, files )
elif type == "textbox": return icon_control_textbox( page, files )
elif type == "dropdown": return icon_control_dropdown( page, files )
elif type == "button": return icon_control_button( page, files )
elif type == "slider": return icon_control_slider( page, files )
elif type == "color": return icon_control_color( args, page, files )
else: return icon_control_default( page, files )
if type == "toggle": return newControlToggle( page, files )
elif type == "toggle_on": return newControlToggleOn( page, files )
elif type == "toggle_off": return newControlToggleOff( page, files )
elif type == "textbox": return newControlTextbox( page, files )
elif type == "dropdown": return newControlDropdown( page, files )
elif type == "button": return newControlButton( page, files )
elif type == "slider": return newControlSlider( page, files )
elif type == "env": return newControlEnvVar( page, files )
elif type == "volume": return newControlVolume( page, files )
elif type == "color": return newControlColor( args, page, files )
else: return newControlDefault( page, files )
raise RuntimeError(f"Unknown type: {type}")
@@ -167,15 +174,19 @@ def Create_Control( args: str, page: Page, files: Files ):
# Create > Option
# #
def Create_Option(type: str):
def badgeOption(type: str):
_, *_, name = re.split(r"[.:]", type)
return f"[`{name}`](#+{type}){{ #+{type} }}\n\n"
# #
# Create > Setting
#
# #### <!-- md:setting example.setting.enabled -->
# <!-- md:version 1.0.0 -->
# <!-- md:default `true` -->
# #
def Create_Setting(type: str):
def badgeSetting(type: str):
_, *_, name = re.split(r"[.*]", type)
return f"`{name}` {{ #{type} }}\n\n[{type}]: #{type}\n\n"
@@ -202,7 +213,7 @@ def _resolve(file: File, page: Page):
# Create > Badge
# #
def Create_Badge(icon: str, text: str = "", type: str = ""):
def badgeCreate(icon: str, text: str = "", type: str = ""):
classes = f"mdx-badge mdx-badge--{type}" if type else "mdx-badge"
return "".join([
f"<span class=\"{classes}\">",
@@ -215,7 +226,7 @@ def Create_Badge(icon: str, text: str = "", type: str = ""):
# Badge > Color Palette
# #
def Badge_ColorPalette(icon: str, text: str = "", type: str = ""):
def badgeColorPalette(icon: str, text: str = "", type: str = ""):
args = type.split( " " )
bg1_clr = "#000000"
@@ -252,10 +263,10 @@ def Badge_ColorPalette(icon: str, text: str = "", type: str = ""):
# <!-- md:sponsors -->
# #
def Badge_Backers(page: Page, files: Files):
def badgeBackers(page: Page, files: Files):
icon = "material-heart"
href = _resolve_path(PAGE_BACKERS, page, files)
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Backers only')",
type = "heart"
)
@@ -280,8 +291,8 @@ def Version( text: str, page: Page, files: Files ):
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
icon = f"[:{icon}:]({href} 'Mkdocs Release')",
return badgeCreate(
icon = f"[:{icon}:]({href} 'TVApp2 Release')",
text = f"[{text}]({_resolve_path(path, page, files)})" if spec else ""
)
@@ -295,7 +306,7 @@ def Version_Stable( text: str, page: Page, files: Files ):
# Return badge
icon = "aetherx-axs-tag"
href = _resolve_path( f"{PAGE_CONVENTIONS}#version-beta", page, files )
href = _resolve_path( f"{PAGE_CONVENTIONS}#version-stable", page, files )
output = ""
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
@@ -306,22 +317,22 @@ def Version_Stable( text: str, page: Page, files: Files ):
else:
output = f"Stable Release"
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} '{output}' )",
text = f"[{spec}]({_resolve_path(path, page, files)})" if spec else ""
)
# #
# Badge > Version > Beta
# Badge > Version > Development
# #
def Version_Beta( text: str, page: Page, files: Files ):
spec = text.replace( "beta-", "" )
def Version_Development( text: str, page: Page, files: Files ):
spec = text.replace( "development-", "" )
path = f"{PAGE_CHANGELOG}#{spec}"
# Return badge
icon = "aetherx-axs-b"
href = _resolve_path( f"{PAGE_CONVENTIONS}#version-beta", page, files )
icon = "aetherx-axs-code"
href = _resolve_path( f"{PAGE_CONVENTIONS}#version-development", page, files )
output = ""
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
@@ -330,9 +341,9 @@ def Version_Beta( text: str, page: Page, files: Files ):
if spec:
output = f"Requires version {spec}"
else:
output = f"Beta Release"
output = f"Development Release"
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} '{output}' )",
text = f"[{text}]({_resolve_path(path, page, files)})" if spec else ""
)
@@ -344,13 +355,13 @@ def Version_Beta( text: str, page: Page, files: Files ):
# <!-- md:feature -->
# #
def Badge_Feature(text: str, page: Page, files: Files):
def badgeFeature(text: str, page: Page, files: Files):
icon = "material-toggle-switch"
href = _resolve_path( f"{PAGE_CONVENTIONS}#feature", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Optional feature')",
text = text
)
@@ -364,32 +375,32 @@ def Badge_Feature(text: str, page: Page, files: Files):
# <!-- md:plugin [typeset] built-in -->
# #
def Badge_Plugin(text: str, page: Page, files: Files):
def badgePlugin(text: str, page: Page, files: Files):
icon = "material-floppy"
href = _resolve_path( f"{PAGE_CONVENTIONS}#plugin", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Plugin')",
text = text
)
# #
# Create badge for extension
# Create badge for Markdown
#
# use the following tag in your md file:
# <!-- md:extension [admonition][Admonition] -->
# <!-- md:markdown [admonition][Admonition] -->
# #
def Badge_Extension(text: str, page: Page, files: Files):
def badgeMarkdown(text: str, page: Page, files: Files):
icon = "material-language-markdown"
href = _resolve_path( f"{PAGE_CONVENTIONS}#extension", page, files)
href = _resolve_path( f"{PAGE_CONVENTIONS}#markdown", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
icon = f"[:{icon}:]({href} 'Markdown extension')",
return badgeCreate(
icon = f"[:{icon}:]({href} 'Markdown functionality')",
text = text
)
@@ -403,13 +414,13 @@ def Badge_Extension(text: str, page: Page, files: Files):
# <!-- md:3rdparty [mike] -->
# #
def Badge_3rdparty(text: str, page: Page, files: Files):
def badge3rdParty(text: str, page: Page, files: Files):
icon = "material-package-variant"
href = _resolve_path( f"{PAGE_CONVENTIONS}#3rdparty", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Third-party utility')",
text = text
)
@@ -433,30 +444,30 @@ def Badge_3rdparty(text: str, page: Page, files: Files):
# <!-- md:example my-example-file -->
# #
def Badge_Example(text: str, page: Page, files: Files):
def badgeExample(text: str, page: Page, files: Files):
return "\n".join([
Badge_Example_Download_Zip(text, page, files),
Badge_Example_View(text, page, files)
badgeExampleDownloadZip(text, page, files),
badgeExampleView(text, page, files)
])
def Badge_Example_View(text: str, page: Page, files: Files):
def badgeExampleView(text: str, page: Page, files: Files):
icon = "material-folder-eye"
href = f"https://github.com/TheBinaryNinja/tvapp2/{text}/"
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'View example')",
type = "right"
)
def Badge_Example_Download_Zip(text: str, page: Page, files: Files):
def badgeExampleDownloadZip(text: str, page: Page, files: Files):
icon = "material-folder-download"
href = f"https://github.com/TheBinaryNinja/tvapp2/{text}.zip"
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Download example')",
text = f"[`.zip`]({href})",
type = "right"
@@ -471,15 +482,16 @@ def Badge_Example_Download_Zip(text: str, page: Page, files: Files):
# <!-- md:command `-s, --start` -->
# #
def Badge_Command(text: str, page: Page, files: Files):
def badgeCommand(text: str, page: Page, files: Files):
icon = "material-console-line"
href = _resolve_path( f"{PAGE_CONVENTIONS}#command", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Terminal / Console Command')",
text = text
text = text,
type = "command"
)
# #
@@ -494,13 +506,13 @@ def Badge_Command(text: str, page: Page, files: Files):
# <!-- md:default none -->
# #
def Badge_DefaultValue_Custom(text: str, page: Page, files: Files):
def badgeDefaultCustom(text: str, page: Page, files: Files):
icon = "material-water"
href = _resolve_path( f"{PAGE_CONVENTIONS}#default", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Default value')",
text = text
)
@@ -517,13 +529,13 @@ def Badge_DefaultValue_Custom(text: str, page: Page, files: Files):
# <!-- md:default none -->
# #
def Badge_DefaultValue_None(page: Page, files: Files):
def badgeDefaultNone(page: Page, files: Files):
icon = "material-water-outline"
href = _resolve_path( f"{PAGE_CONVENTIONS}#default", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Default value is empty')"
)
@@ -539,53 +551,127 @@ def Badge_DefaultValue_None(page: Page, files: Files):
# <!-- md:default none -->
# #
def Badge_DefaultValue_Computed(page: Page, files: Files):
def badgeDefaultVal(page: Page, files: Files):
icon = "material-water-check"
href = _resolve_path( f"{PAGE_CONVENTIONS}#default", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Default value is computed')"
)
# #
# Badge > Flag > Default
#
# This symbol denotes that the specified item is a customizable setting
#
# MUST add an entry in conventions.md
#
# use the following tag in your md file:
# : <!-- md:flag --> Default
# : <!-- md:flag experimental --> Experimental
# : <!-- md:flag required --> Required
# : <!-- md:flag customization --> Customization
# : <!-- md:flag metadata --> Metadata
# : <!-- md:flag dangerous --> Dangerous
# : <!-- md:flag multiple --> Multiple
# : <!-- md:flag setting --> Setting
# #
def badgeFlagDefault(page: Page, files: Files):
icon = "material-flag"
href = _resolve_path( f"{PAGE_CONVENTIONS}#setting", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return badgeCreate(
icon = f"[:{icon}:]({href} 'Configurable Setting')"
)
# #
# Badge > Flag > Metadata Property
#
# This symbol denotes that the item described is a metadata property, which can
# be used in Markdown documents as part of the front matter definition.
#
# MUST add an entry in conventions.md
#
# use the following tag in your md file:
# <!-- md:flag metadata -->
# : <!-- md:flag --> Default
# : <!-- md:flag experimental --> Experimental
# : <!-- md:flag required --> Required
# : <!-- md:flag customization --> Customization
# : <!-- md:flag metadata --> Metadata
# : <!-- md:flag dangerous --> Dangerous
# : <!-- md:flag multiple --> Multiple
# : <!-- md:flag setting --> Setting
# #
def Badge_Flag_Metadata(page: Page, files: Files):
def badgeFlagMetadata(page: Page, files: Files):
icon = "material-list-box-outline"
href = _resolve_path( f"{PAGE_CONVENTIONS}#metadata", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Metadata property')"
)
# #
# Badge > Flag > Dangerous
#
# This symbol denotes that the item or setting specified may be dangerous to change.
#
# MUST add an entry in conventions.md
#
# use the following tag in your md file:
# : <!-- md:flag --> Default
# : <!-- md:flag experimental --> Experimental
# : <!-- md:flag required --> Required
# : <!-- md:flag customization --> Customization
# : <!-- md:flag metadata --> Metadata
# : <!-- md:flag dangerous --> Dangerous
# : <!-- md:flag multiple --> Multiple
# : <!-- md:flag setting --> Setting
# #
def badgeFlagDangerous(page: Page, files: Files):
icon = "aetherx-axd-skull-crossbones"
href = _resolve_path( f"{PAGE_CONVENTIONS}#dangerous", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return badgeCreate(
icon = f"[:{icon}:]({href} 'This setting is dangerous to change')",
type = "dangerous"
)
# #
# Badge > Flag > Required
#
# Specifies that a value is required.
#
# MUST add an entry in conventions.md
#
# use the following tag in your md file:
# <!-- md:flag required -->
# <!-- md:flag required --> This option enables the content tabs
# : <!-- md:flag --> Default
# : <!-- md:flag experimental --> Experimental
# : <!-- md:flag required --> Required
# : <!-- md:flag customization --> Customization
# : <!-- md:flag metadata --> Metadata
# : <!-- md:flag dangerous --> Dangerous
# : <!-- md:flag multiple --> Multiple
# : <!-- md:flag setting --> Setting
# #
def Badge_Flag_Required(page: Page, files: Files):
def badgeFlagRequired(page: Page, files: Files):
icon = "material-alert"
href = _resolve_path( f"{PAGE_CONVENTIONS}#required", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Required value')"
)
@@ -594,17 +680,26 @@ def Badge_Flag_Required(page: Page, files: Files):
#
# This symbol denotes that the item described is a customization which affects the overall look of the app.
#
# MUST add an entry in conventions.md
#
# use the following tag in your md file:
# <!-- md:flag customization -->
# : <!-- md:flag --> Default
# : <!-- md:flag experimental --> Experimental
# : <!-- md:flag required --> Required
# : <!-- md:flag customization --> Customization
# : <!-- md:flag metadata --> Metadata
# : <!-- md:flag dangerous --> Dangerous
# : <!-- md:flag multiple --> Multiple
# : <!-- md:flag setting --> Setting
# #
def Badge_Flag_Customization(page: Page, files: Files):
def badgeFlagCustomization(page: Page, files: Files):
icon = "material-brush-variant"
href = _resolve_path( f"{PAGE_CONVENTIONS}#customization", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Customization')"
)
@@ -616,16 +711,23 @@ def Badge_Flag_Customization(page: Page, files: Files):
# MUST add an entry in conventions.md
#
# use the following tag in your md file:
# <!-- md:flag experimental -->
# : <!-- md:flag --> Default
# : <!-- md:flag experimental --> Experimental
# : <!-- md:flag required --> Required
# : <!-- md:flag customization --> Customization
# : <!-- md:flag metadata --> Metadata
# : <!-- md:flag dangerous --> Dangerous
# : <!-- md:flag multiple --> Multiple
# : <!-- md:flag setting --> Setting
# #
def Badge_Flag_Experimental(page: Page, files: Files):
def badgeFlagExperimental(page: Page, files: Files):
icon = "material-flask-outline"
href = _resolve_path( f"{PAGE_CONVENTIONS}#experimental", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Experimental')"
)
@@ -638,16 +740,23 @@ def Badge_Flag_Experimental(page: Page, files: Files):
# MUST add an entry in conventions.md
#
# use the following tag in your md file:
# <!-- md:flag multiple -->
# : <!-- md:flag --> Default
# : <!-- md:flag experimental --> Experimental
# : <!-- md:flag required --> Required
# : <!-- md:flag customization --> Customization
# : <!-- md:flag metadata --> Metadata
# : <!-- md:flag dangerous --> Dangerous
# : <!-- md:flag multiple --> Multiple
# : <!-- md:flag setting --> Setting
# #
def Badge_Flag_Multiple_Instances(page: Page, files: Files):
def badgeFlagMultiInstances(page: Page, files: Files):
icon = "material-inbox-multiple"
href = _resolve_path( f"{PAGE_CONVENTIONS}#multiple-instances", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Multiple instances')"
)
@@ -659,16 +768,23 @@ def Badge_Flag_Multiple_Instances(page: Page, files: Files):
# MUST add an entry in conventions.md
#
# use the following tag in your md file:
# <!-- md:flag setting -->
# : <!-- md:flag --> Default
# : <!-- md:flag experimental --> Experimental
# : <!-- md:flag required --> Required
# : <!-- md:flag customization --> Customization
# : <!-- md:flag metadata --> Metadata
# : <!-- md:flag dangerous --> Dangerous
# : <!-- md:flag multiple --> Multiple
# : <!-- md:flag setting --> Setting
# #
def Badge_Flag_Setting(page: Page, files: Files):
def badgeFlagSetting(page: Page, files: Files):
icon = "material-cog"
href = _resolve_path( f"{PAGE_CONVENTIONS}#setting", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Configurable Setting')"
)
@@ -681,13 +797,13 @@ def Badge_Flag_Setting(page: Page, files: Files):
# <!-- md:control -->
# #
def icon_control_default( page: Page, files: Files ):
def newControlDefault( page: Page, files: Files ):
icon = "aetherx-axs-hand-pointer"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files )
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Type: Textbox')"
)
@@ -698,13 +814,13 @@ def icon_control_default( page: Page, files: Files ):
# <!-- md:control textbox -->
# #
def icon_control_textbox( page: Page, files: Files ):
def newControlTextbox( page: Page, files: Files ):
icon = "aetherx-axs-input-text"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files )
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Type: Textbox')"
)
@@ -717,33 +833,33 @@ def icon_control_textbox( page: Page, files: Files ):
# <!-- md:control toggle_off --> `Disabled`
# #
def icon_control_toggle( page: Page, files: Files ):
def newControlToggle( page: Page, files: Files ):
icon = "aetherx-axs-toggle-large-on"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Type: Toggle Switch')"
)
def icon_control_toggle_on( page: Page, files: Files ):
def newControlToggleOn( page: Page, files: Files ):
icon = "aetherx-axd-toggle-on"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Toggle: Enabled')"
)
def icon_control_toggle_off( page: Page, files: Files ):
def newControlToggleOff( page: Page, files: Files ):
icon = "aetherx-axd-toggle-off"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Toggle: Disabled')"
)
@@ -754,13 +870,13 @@ def icon_control_toggle_off( page: Page, files: Files ):
# <!-- md:control dropdown -->
# #
def icon_control_dropdown( page: Page, files: Files ):
def newControlDropdown( page: Page, files: Files ):
icon = "aetherx-axs-square-caret-down"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files)
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Type: Dropdown')"
)
@@ -771,13 +887,13 @@ def icon_control_dropdown( page: Page, files: Files ):
# <!-- md:control button -->
# #
def icon_control_button( page: Page, files: Files ):
def newControlButton( page: Page, files: Files ):
icon = "material-button-pointer"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files )
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Type: Button')"
)
@@ -788,13 +904,13 @@ def icon_control_button( page: Page, files: Files ):
# <!-- md:control slider -->
# #
def icon_control_slider( page: Page, files: Files ):
def newControlSlider( page: Page, files: Files ):
icon = "aetherx-axd-sliders-simple"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files )
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Create_Badge(
return badgeCreate(
icon = f"[:{icon}:]({href} 'Type: Slider')"
)
@@ -805,13 +921,49 @@ def icon_control_slider( page: Page, files: Files ):
# <!-- md:control color #E5E5E5 #121315 -->
# #
def icon_control_color( text: str, page: Page, files: Files ):
def newControlColor( text: str, page: Page, files: Files ):
icon = "aetherx-axs-palette"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files )
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return Badge_ColorPalette(
return badgeColorPalette(
icon = f"[:{icon}:]({href} 'Type: Color Wheel')",
type = text
)
# #
# Icon : Control : Env Variable
#
# use the following tag in your md file:
# <!-- md:control env -->
# #
def newControlEnvVar( page: Page, files: Files ):
icon = "aetherx-axd-puzzle-piece"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files )
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return badgeCreate(
icon = f"[:{icon}:]({href} 'Type: Environment Variable')",
type = "env"
)
# #
# Icon : Control : Volume
#
# use the following tag in your md file:
# <!-- md:control volume -->
# #
def newControlVolume( page: Page, files: Files ):
icon = "aetherx-axd-volume"
href = _resolve_path( f"{PAGE_CONVENTIONS}#control", page, files )
print(clr.MAGENTA + 'VERBOSE - ' + clr.WHITE + ' Running ' + clr.YELLOW + inspect.stack()[0][3] + clr.WHITE + ' for page ' + clr.GREY + str(href) + clr.WHITE )
return badgeCreate(
icon = f"[:{icon}:]({href} 'Type: Mountable Volume')",
type = "volume"
)

View File

@@ -1,10 +1,35 @@
# #
# Mkdocs Config
#
# @notes this documentation requires the following plugins to be installed:
# pip install mkdocs
# pip install mkdocs-material
# pip install mike
# pip install mkdocs-git-committers-plugin-2
# pip install mkdocs-encryptcontent-plugin
# pip install mkdocs-embed-external-markdown
# pip install mkdocs-redirects mkdocs-glightbox pymdown-extensions mkdocs-git-revision-date-localized-plugin mkdocs-git-authors-plugin mkdocs-link-embeds-plugin
# cd docs
# mkdocs build
#
# @update use the following commands to update mkdocs and the mkdocs-material theme:
# pip install --upgrade mkdocs
# pip install --upgrade --force-reinstall mkdocs-material
# #
site_name: !!python/object/apply:os.getenv ["DOCS_NAME", "TVApp2"]
repo_url: https://github.com/TheBinaryNinja/tvapp2
repo_name: TheBinaryNinja/tvapp2
edit_uri: edit/main/docs/docs/
site_author: TheBinaryNinja
site_url: 'https://thebinaryninja.github.io/tvapp2/'
# #
# Section Markdown Extensions
# #
markdown_extensions:
- pymdownx.critic
- markdown.extensions.extra
- toc:
permalink: true
@@ -62,11 +87,15 @@ markdown_extensions:
- pymdownx.keys
- pymdownx.snippets:
auto_append:
- includes/abbreviations.md
- docs/includes/abbreviations.md
url_download: true
- pymdownx.arithmatex:
generic: true
# #
# Section Theme
# #
theme:
highlightjs: true
hljs_languages:
@@ -74,6 +103,12 @@ theme:
- yml
- rust
- markdown
- json
- batch
- typescript
- javascript
- lua
- python
icon:
note: fontawesome/solid/note-sticky
abstract: fontawesome/solid/book
@@ -88,6 +123,7 @@ theme:
example: fontawesome/solid/flask
quote: fontawesome/solid/quote-left
annotation: material/arrow-right-circle
hi: aetherx/axs/clock
tag:
html: fontawesome/brands/html5
js: fontawesome/brands/js
@@ -116,8 +152,10 @@ theme:
code: Roboto Mono
favicon: assets/favicon.png
icon:
logo: logo
logo: /aetherx/axd/tv
features:
- navigation.tabs
- navigation.sections
- announce.dismiss
- toc.follow
- content.action.edit
@@ -138,33 +176,88 @@ theme:
- navigation.tracking
- navigation.path
- navigation.top
# #
# Section Hooks
# #
# Hooks
hooks:
- material/overrides/hooks/shortcodes.py
- material/overrides/hooks/translations.py
# #
# Section Extra CSS
# #
extra_css:
- stylesheets/extra.css?v1.000
# #
# Section Extra Javascript
# #
extra_javascript:
- https://unpkg.com/tablesort@5.3.0/dist/tablesort.min.js
- javascripts/tablesort.js
# #
# Section Extras
# #
extra:
social:
- icon: fontawesome/brands/github
link: https://github.com/TheBinaryNinja/tvapp2
- icon: fontawesome/brands/discord
link: https://discord.gg/gTze6hRe
tags:
HTML5: html
JavaScript: js
CSS: css
generator: false
version:
default: latest
provider: mike
alias: true
consent:
title: Cookie Consent
description: >-
We use cookies to recognize your repeated visits and preferences, as well
as to measure the effectiveness of our documentation and whether users
find what they're searching for. With your consent, you're helping us to
make our documentation better.
# #
# Section Navigation
# #
nav:
- Home: 'home.md'
- About TVApp2: 'about_tvapp2.md'
- About:
- What Is TVApp2: 'about/what_is_tvapp.md'
- Contributing: 'about/contributing.md'
- License: 'about/license.md'
- Wiki:
- Conventions: 'about/conventions.md'
- Tags: 'about/tags.md'
- Install:
- Getting Started: 'install/index.md'
- docker run: 'install/docker-run.md'
- docker compose: 'install/docker-compose.md'
- Config:
- Environment Variables: 'config/env.md'
- Volumes: 'config/volumes.md'
- Usage:
- Healthcheck: 'usage/healthcheck.md'
- Changelog: 'about/changelog.md'
- Backers: 'backers/index.md'
- Discord: 'https://discord.gg/gTze6hRe'
# #
# Section Plugins
# #
plugins:
- search:
separator: '[\s\u200b\-_,:!=\[\]()"`/]+|\.(?!\d)|&[lg]t;|(?!\b)(?=[A-Z][a-z])'
- tags:
tags_file: about/tags.md
- group:
plugins:
- optimize
@@ -235,28 +328,12 @@ plugins:
image_default: "https://github.com/Aetherinox/mkdocs-link-embeds/assets/118329232/c0298d98-0910-4235-a88f-0c3e2f704ba7"
image_disabled: false
favicon_default: "https://github.com/Aetherinox/mkdocs-link-embeds/assets/118329232/b37da9c6-6f17-4c3f-9c94-c346a6f31bfa"
favicon_disabled: true
favicon_disabled: false
favicon_size: 25
target: "blank"
accent: "FFFFFF1A"
verbose: true
extra:
tags:
HTML5: html
JavaScript: js
CSS: css
generator: false
version:
default: stable
provider: mike
consent:
title: Cookie Consent
description: >-
We use cookies to recognize your repeated visits and preferences, as well
as to measure the effectiveness of our documentation and whether users
find what they're searching for. With your consent, you're helping us to
make our documentation better.
- external-markdown
copyright: >
Copyright &copy; 2025 - BinaryNinja

View File

@@ -6,7 +6,7 @@ MODE con:cols=125 lines=120
MODE 125,40
GOTO comment_end
Starts up mkdocs from a windows system.
@usage Starts up mkdocs from a windows system.
Ensure you have defined `GH_TOKEN` or the git-committers plugin will rate limit you.
setx /m GH_TOKEN "github_pat_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
@@ -16,6 +16,10 @@ and give it your Github fine-grained personal access token.
The token variable is defined in mkdocs.yml
@update use the following commands to update mkdocs and the mkdocs-material theme:
pip install --upgrade mkdocs
pip install --upgrade --force-reinstall mkdocs-material
:comment_end
ECHO.
@@ -30,28 +34,33 @@ set dir_home=%~dp0
:: define: env variable
:: #
set TOKEN=%GH_TOKEN2%
echo --------------------------------------------------------------------------------
echo ------------------------------------------------------------------------------------------------
echo Mkdocs Launcher
echo --------------------------------------------------------------------------------
IF [!TOKEN!]==[] (
echo --------------------------------------------------------------------------------
echo GH_TOKEN not defined. Open %0%
echo Create a new one at https://github.com/settings/personal-access-tokens
echo --------------------------------------------------------------------------------
set /P TOKEN= Enter Github Personal Access Token (fine-grained):
echo ------------------------------------------------------------------------------------------------
IF "!GH_TOKEN!"=="" (
echo GH_TOKEN not defined.
echo Open %0%
echo Create a new one at:
echo https://github.com/settings/personal-access-tokens
echo ------------------------------------------------------------------------------------------------
set /p TOKEN=" Enter Github Personal Access Token (fine-grained): "
)
echo GH_TOKEN: !TOKEN!
echo GH_TOKEN: !GH_TOKEN!
echo.
echo.
echo Creating environment variable GH_TOKEN
setx GH_TOKEN "!GH_TOKEN!"
timeout 2 > NUL
:: #
:: start mkdocs
:: #
echo Starting mkdocs ...
start cmd /k "mkdocs serve --clean"
timeout 5 > NUL

83
renovate.json Normal file
View File

@@ -0,0 +1,83 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
":preserveSemverRanges"
],
"timezone": "Etc/UTC",
"includeForks": true,
"forkProcessing": "enabled",
"baseBranches": ["main"],
"prCreation": "immediate",
"rebaseWhen": "conflicted",
"rebaseLabel": "AC Needs Rebase",
"gitAuthor": "EuropaServ <161414668+EuropaServ@users.noreply.github.com>",
"updatePinnedDependencies": false,
"dependencyDashboard": true,
"dependencyDashboardTitle": "📁 Dependency Dashboard",
"dependencyDashboardLabels": ["📰 Progress Report"],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["Type ◦ Vulnerability"]
},
"labels": [
"Type ◦ Dependency"
],
"major": {
"automerge": false
},
"lockFileMaintenance": {
"enabled": true,
"automerge": true
},
"packageRules": [
{
"matchManagers": [
"nodenv",
"npm",
"nvm"
],
"addLabels": [
"Type ◦ Dependency"
],
"rangeStrategy": "auto"
},
{
"matchManagers": [
"nodenv",
"npm",
"nvm"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"addLabels": [
"Type ◦ Dependency"
],
"automerge": false
},
{
"matchManagers": [
"github-actions"
],
"addLabels": [
"Type ◦ Git Action"
],
"rangeStrategy": "auto"
},
{
"matchManagers": [
"github-actions"
],
"addLabels": [
"Type ◦ Git Action"
],
"matchUpdateTypes": [
"minor",
"patch"
],
"automerge": false
}
]
}

View File

@@ -1,5 +1,18 @@
#!/usr/bin/with-contenv sh
# #
# Store env variables in s6
# #
ip_gateway=$(/sbin/ip route|awk '/default/ { print $3 }')
ip_container=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1')
printf "$ip_gateway" > /var/run/s6/container_environment/IP_GATEWAY
printf "$ip_container" > /var/run/s6/container_environment/IP_CONTAINER
export IP_GATEWAY=$ip_gateway
export IP_GATEWAY=$ip_container
# #
# install and startup for tvapp2
# #
@@ -7,4 +20,5 @@
cp -r ${DIR_BUILD}/* ${DIR_RUN}
rm -rf ${DIR_BUILD}/*
cd ${DIR_RUN}
npm install --omit=dev
npm start

273
tvapp2/eslint.config.mjs Normal file
View File

@@ -0,0 +1,273 @@
/*
Eslint 9 Flat Config
old eslint < 8 .rc files are no longer supported! do not place .eslintrc files in subfolders.
eslint developers are currently working on an experimental feature to allow for sub-folder
override rules
@ref https://github.com/eslint/eslint/discussions/18574#discussioncomment-9729092
https://eslint.org/docs/latest/use/configure/configuration-files#experimental-configuration-file-resolution
eslint config migration docs
@ref https://eslint.org/docs/latest/use/configure/migration-guide
*/
import path from 'path';
import globals from 'globals';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
/*
Plugins
*/
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'
/*
Globals
*/
const customGlobals =
{
guid: 'readable',
uuid: 'readable',
Buffer: "readonly",
BufferEncoding: "readonly"
};
/*
Compatibility
*/
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url); // get resolved path to file
const __dirname = path.dirname(__filename); // get name of directory
const compat = new FlatCompat({
baseDirectory: __dirname, // optional; default: process.cwd()
resolvePluginsRelativeTo: __dirname, // optional
recommendedConfig: js.configs.recommended, // optional unless using 'eslint:recommended'
allConfig: js.configs.all, // optional unless using 'eslint:all'
});
/*
Eslint > Flat Config
*/
export default
[
{
ignores: [
'coverage/**',
'node_modules/**',
'**/dist/**/*',
'**/__tmp__/**/*',
'eslint.config.mjs',
'eslint.config.cjs',
"root.js",
"www/**/*"
]
},
...compat.extends('eslint:recommended'),
{
plugins: {
'n': pluginNode,
'import': pluginImport,
'@stylistic/js': pluginStylisticJs,
'chai-friendly': pluginChaiFriendly
},
linterOptions: {
reportUnusedDisableDirectives: false
},
languageOptions: {
globals: {
...customGlobals,
...globals.browser,
process: true, // Node.js global
_: true,
$: true
},
sourceType: 'module',
ecmaVersion: 'latest',
parserOptions: {
requireConfigFile: false
}
},
rules: {
// eslint/js rules
'one-var': 'off',
'no-throw-literal': 'off',
'camelcase': [
'error',
{
'properties': 'always'
}
],
'no-unused-vars': 'off',
'no-console': 'off',
'no-alert': 'error',
'no-debugger': 'error',
'prefer-arrow-callback': 'error',
'no-useless-escape': 'off',
'no-var': 'error',
'prefer-const': 'error',
'no-unused-expressions': 0,
'chai-friendly/no-unused-expressions': 'off',
'strict': ['error', 'never'],
'prefer-promise-reject-errors': 'off',
'no-object-constructor': 'error',
'object-shorthand': 'off',
'no-array-constructor': 'error',
'array-callback-return': 'error',
'no-eval': 'error',
'no-new-func': 'error',
'prefer-rest-params': 'error',
'prefer-spread': 'error',
'no-useless-constructor': 'error',
'no-dupe-class-members': 'error',
'no-duplicate-imports': 'error',
'eqeqeq': 'error',
'no-unneeded-ternary': 'error',
'curly': 'off',
'no-empty': 'off',
'no-restricted-syntax': [
'error',
{
'selector': 'ExportDefaultDeclaration',
'message': 'Prefer named exports'
}
],
'import/no-webpack-loader-syntax': 'off',
'import/no-relative-parent-imports': 'error',
'import/first': 'error',
'import/no-default-export': 'error',
'node/no-callback-literal': 0,
/*
@plugin eslint-plugin-n
*/
'n/no-callback-literal': 0,
'n/no-deprecated-api': 'error',
'n/no-exports-assign': 'error',
'n/no-extraneous-import': 'error',
'n/no-extraneous-require': [
'error',
{
'allowModules': ['electron', 'electron-notarize'],
'resolvePaths': [],
'tryExtensions': []
}
],
'n/no-missing-import': 'off',
'n/no-missing-require': 'off',
'n/no-mixed-requires': 'error',
'n/no-new-require': 'error',
'n/no-path-concat': 'error',
'n/no-process-env': 'off',
'n/no-process-exit': 'off',
'n/no-restricted-import': 'error',
'n/no-restricted-require': 'error',
'n/no-sync': 'off',
'n/no-unpublished-bin': 'error',
'n/no-unpublished-import': 'error',
'n/no-unpublished-require': 'error',
'n/no-unsupported-features/es-builtins': 'error',
'n/no-unsupported-features/es-syntax': 'error',
'n/no-unsupported-features/node-builtins': 'off',
'n/prefer-global/buffer': 'error',
'n/prefer-global/console': 'error',
'n/prefer-global/process': 'error',
'n/prefer-global/text-decoder': 'error',
'n/prefer-global/text-encoder': 'error',
'n/prefer-global/url': 'error',
'n/prefer-global/url-search-params': 'error',
'n/prefer-node-protocol': 'off',
'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', {
before: false,
after: false,
}],
"@stylistic/js/space-before-function-paren": ["error", {
anonymous: "always",
asyncArrow: "never",
named: "never"
}],
'@stylistic/js/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',
{
exceptions: ["{}", "[]"]
}],
'@stylistic/js/spaced-comment': [ 'error', 'always',
{
markers: ['/']
}],
'@stylistic/js/array-bracket-newline': [ 'warn',
{
multiline: true,
minItems: 5,
}],
'@stylistic/js/brace-style': [ 'error', 'allman',
{
allowSingleLine: true,
}],
'@stylistic/js/array-bracket-spacing': [ 'error', 'always',
{
arraysInArrays: false,
objectsInArrays: false,
singleValue: false,
}],
'@stylistic/js/wrap-iife': [2, 'inside', { functionPrototypeMethods: true }],
'@stylistic/js/keyword-spacing': [ 'error',
{
before: true,
after: true,
overrides:
{
return: { before: true, after: true },
throw: { before: true, after: true },
case: { before: true, after: true },
as: { before: true, after: true },
if: { before: true, after: true },
for: { before: true, after: true },
while: { before: true, after: true },
static: { before: true, after: true }
}
}],
},
}
];

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

View File

@@ -3,31 +3,43 @@
"browsers": [
{
"name": "chromium",
"revision": "1155",
"revision": "1169",
"installByDefault": true,
"browserVersion": "133.0.6943.16"
"browserVersion": "136.0.7103.25"
},
{
"name": "chromium-headless-shell",
"revision": "1169",
"installByDefault": true,
"browserVersion": "136.0.7103.25"
},
{
"name": "chromium-tip-of-tree",
"revision": "1293",
"revision": "1320",
"installByDefault": false,
"browserVersion": "133.0.6943.0"
"browserVersion": "137.0.7105.0"
},
{
"name": "chromium-tip-of-tree-headless-shell",
"revision": "1320",
"installByDefault": false,
"browserVersion": "137.0.7105.0"
},
{
"name": "firefox",
"revision": "1471",
"revision": "1482",
"installByDefault": true,
"browserVersion": "134.0"
"browserVersion": "137.0"
},
{
"name": "firefox-beta",
"revision": "1467",
"revision": "1477",
"installByDefault": false,
"browserVersion": "133.0b9"
"browserVersion": "137.0b2"
},
{
"name": "webkit",
"revision": "2123",
"revision": "2158",
"installByDefault": true,
"revisionOverrides": {
"debian11-x64": "2105",
@@ -38,10 +50,12 @@
"mac11-arm64": "1816",
"mac12": "2009",
"mac12-arm64": "2009",
"mac13": "2140",
"mac13-arm64": "2140",
"ubuntu20.04-x64": "2092",
"ubuntu20.04-arm64": "2092"
},
"browserVersion": "18.2"
"browserVersion": "18.4"
},
{
"name": "ffmpeg",

View File

@@ -19,7 +19,6 @@ const semver = currentNodeVersion.split('.');
const [major] = [+semver[0]];
if (major < minimumMajorNodeVersion) {
// eslint-disable-next-line no-console
console.error(
'You are running Node.js ' +
currentNodeVersion +

View File

@@ -1,69 +1,63 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.AndroidServerLauncherImpl = void 0;
var _utilsBundle = require("./utilsBundle");
var _utils = require("./utils");
var _playwright = require("./server/playwright");
var _playwrightServer = require("./remote/playwrightServer");
/**
* 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.
*/
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, _playwright.createPlaywright)({
sdkLanguage: 'javascript',
isServer: true
});
// 1. Pre-connect to the device
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 (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`);
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`);
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, _utils.createGuid)()}`;
// 2. Start the server
const server = new _playwrightServer.PlaywrightServer({
mode: 'launchServer',
path,
maxConnections: 1,
preLaunchedAndroidDevice: device
});
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);
// 3. Return the BrowserServer interface
const browserServer = new _utilsBundle.ws.EventEmitter();
const browserServer = new import_utilsBundle.ws.EventEmitter();
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => device.close();
browserServer.kill = () => device.close();
device.on('close', () => {
device.on("close", () => {
server.close();
browserServer.emit('close');
browserServer.emit("close");
});
return browserServer;
}
}
exports.AndroidServerLauncherImpl = AndroidServerLauncherImpl;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
AndroidServerLauncherImpl
});

View File

@@ -1,74 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.BrowserServerLauncherImpl = void 0;
var _utilsBundle = require("./utilsBundle");
var _clientHelper = require("./client/clientHelper");
var _utils = require("./utils");
var _instrumentation = require("./server/instrumentation");
var _playwright = require("./server/playwright");
var _playwrightServer = require("./remote/playwrightServer");
var _helper = require("./server/helper");
var _stackTrace = require("./utils/stackTrace");
var _socksProxy = require("./common/socksProxy");
/**
* 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.
*/
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 = void 0;
this._browserName = browserName;
}
async launchServer(options = {}) {
const playwright = (0, _playwright.createPlaywright)({
sdkLanguage: 'javascript',
isServer: true
});
// TODO: enable socks proxy once ipv6 is supported.
const socksProxy = false ? new _socksProxy.SocksProxy() : undefined;
playwright.options.socksProxyPort = await (socksProxy === null || socksProxy === void 0 ? void 0 : socksProxy.listen(0));
// 1. Pre-launch the browser
const metadata = (0, _instrumentation.serverSideCallMetadata)();
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 : undefined,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined
}, toProtocolLogger(options.logger)).catch(e => {
const log = _helper.helper.formatBrowserLogs(metadata.log);
(0, _stackTrace.rewriteErrorMessage)(e, `${e.message} Failed to launch browser.${log}`);
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, _utils.createGuid)()}`;
// 2. Start the server
const server = new _playwrightServer.PlaywrightServer({
mode: 'launchServer',
path,
maxConnections: Infinity,
preLaunchedBrowser: browser,
preLaunchedSocksProxy: socksProxy
});
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);
// 3. Return the BrowserServer interface
const browserServer = new _utilsBundle.ws.EventEmitter();
const browserServer = new import_utilsBundle.ws.EventEmitter();
browserServer.process = () => browser.options.browserProcess.process;
browserServer.wsEndpoint = () => wsEndpoint;
browserServer.close = () => browser.options.browserProcess.close();
@@ -77,16 +60,29 @@ class BrowserServerLauncherImpl {
browserServer._disconnectForTest = () => server.close();
browserServer._userDataDirForTest = browser._userDataDirForTest;
browser.options.browserProcess.onclose = (exitCode, signal) => {
socksProxy === null || socksProxy === void 0 || socksProxy.close().catch(() => {});
socksProxy?.close().catch(() => {
});
server.close();
browserServer.emit('close', exitCode, signal);
browserServer.emit("close", exitCode, signal);
};
return browserServer;
}
}
exports.BrowserServerLauncherImpl = BrowserServerLauncherImpl;
function toProtocolLogger(logger) {
return logger ? (direction, message) => {
if (logger.isEnabled('protocol', 'verbose')) logger.log('protocol', 'verbose', (direction === 'send' ? 'SEND ► ' : '◀ RECV ') + JSON.stringify(message), [], {});
} : undefined;
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,95 +1,97 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.launchBrowserServer = launchBrowserServer;
exports.printApiJson = printApiJson;
exports.runDriver = runDriver;
exports.runServer = runServer;
var _fs = _interopRequireDefault(require("fs"));
var playwright = _interopRequireWildcard(require("../.."));
var _server = require("../server");
var _transport = require("../protocol/transport");
var _playwrightServer = require("../remote/playwrightServer");
var _processLauncher = require("../utils/processLauncher");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
/* eslint-disable no-console */
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() {
// Note: this file is generated by build-playwright-driver.sh
console.log(JSON.stringify(require('../../api.json')));
console.log(JSON.stringify(require("../../api.json")));
}
function runDriver() {
const dispatcherConnection = new _server.DispatcherConnection();
new _server.RootDispatcher(dispatcherConnection, async (rootScope, {
sdkLanguage
}) => {
const playwright = (0, _server.createPlaywright)({
sdkLanguage
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);
});
return new _server.PlaywrightDispatcher(rootScope, playwright);
});
const transport = new _transport.PipeTransport(process.stdout, process.stdin);
transport.onmessage = message => dispatcherConnection.dispatch(JSON.parse(message));
// Certain Language Binding JSON parsers (e.g. .NET) do not like strings with lone surrogates.
const isJavaScriptLanguageBinding = !process.env.PW_LANG_NAME || process.env.PW_LANG_NAME === 'javascript';
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();
if (typeof value === "string")
return value.toWellFormed();
return value;
} : undefined;
dispatcherConnection.onmessage = message => transport.send(JSON.stringify(message, replacer));
} : void 0;
dispatcherConnection.onmessage = (message) => transport.send(JSON.stringify(message, replacer));
transport.onclose = () => {
// Drop any messages during shutdown on the floor.
dispatcherConnection.onmessage = () => {};
(0, _processLauncher.gracefullyProcessExitDoNotHang)(0);
dispatcherConnection.onmessage = () => {
};
// Ignore the SIGINT signal in the driver process so the parent can gracefully close the connection.
// We still will destruct everything (close browsers and exit) when the transport pipe closes.
process.on('SIGINT', () => {
// Keep the process running.
(0, import_processLauncher.gracefullyProcessExitDoNotHang)(0);
};
process.on("SIGINT", () => {
});
}
async function runServer(options) {
const {
port,
host,
path = '/',
path = "/",
maxConnections = Infinity,
extension
} = options;
const server = new _playwrightServer.PlaywrightServer({
mode: extension ? 'extension' : 'default',
path,
maxConnections
});
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, _processLauncher.gracefullyProcessExitDoNotHang)(0));
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(_fs.default.readFileSync(configFile).toString());
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,134 +1,173 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "program", {
enumerable: true,
get: function () {
return _utilsBundle.program;
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
});
var _fs = _interopRequireDefault(require("fs"));
var _os = _interopRequireDefault(require("os"));
var _path = _interopRequireDefault(require("path"));
var _utilsBundle = require("../utilsBundle");
var _driver = require("./driver");
var _traceViewer = require("../server/trace/viewer/traceViewer");
var playwright = _interopRequireWildcard(require("../.."));
var _utils = require("../utils");
var _server = require("../server");
var _errors = require("../client/errors");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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.
*/
/* eslint-disable no-console */
const packageJSON = require('../../package.json');
_utilsBundle.program.version('Version ' + (process.env.PW_CLI_DISPLAY_VERSION || packageJSON.version)).name(buildBasePlaywrightCLICommand(process.env.PW_LANG_NAME));
_utilsBundle.program.command('mark-docker-image [dockerImageNameTemplate]', {
hidden: true
}).description('mark docker image').allowUnknownOption(true).action(function (dockerImageNameTemplate) {
(0, _utils.assert)(dockerImageNameTemplate, 'dockerImageNameTemplate is required');
(0, _server.writeDockerVersion)(dockerImageNameTemplate).catch(logErrorAndExit);
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) {
commandWithOpenOptions("open [url]", "open page in browser specified via -b, --browser", []).action(function(url, options) {
open(options, url, codegenId()).catch(logErrorAndExit);
}).addHelpText('afterAll', `
}).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) {
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', `
}).addHelpText("afterAll", `
Examples:
$ codegen
$ codegen --target=python
$ codegen -b webkit https://example.com`);
function suggestedBrowsersToInstall() {
return _server.registry.executables().filter(e => e.installType !== 'none' && e.type !== 'tool').map(e => e.name).join(', ');
return import_server.registry.executables().filter((e) => e.installType !== "none" && e.type !== "tool").map((e) => e.name).join(", ");
}
function defaultBrowsersToInstall(options) {
let executables = _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');
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`);
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 = _server.registry.findExecutable(arg);
if (!executable || executable.installType === 'none') faultyArguments.push(arg);else executables.push(executable);
if ((executable === null || executable === void 0 ? void 0 : executable.browserName) === 'chromium') executables.push(_server.registry.findExecutable('ffmpeg'));
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');
if (arg === "chromium") {
if (!options.onlyShell)
handleArgument("chromium");
if (!options.noShell)
handleArgument("chromium-headless-shell");
} else {
handleArgument(arg);
}
}
if (process.platform === 'win32') executables.push(_server.registry.findExecutable('winldd'));
if (faultyArguments.length) throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall()}`);
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;
}
_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) {
// For '--no-shell' option, commander sets `shell: false` instead.
if (options.shell === false) options.noShell = true;
if ((0, _utils.isLikelyNpxGlobal)()) {
console.error((0, _utils.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));
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 _server.registry.installDeps(executables, !!options.dryRun);
if (options.withDeps)
await import_server.registry.installDeps(executables, !!options.dryRun);
if (options.dryRun) {
for (const executable of executables) {
var _executable$directory, _executable$downloadU;
const version = executable.browserVersion ? `version ` + executable.browserVersion : '';
console.log(`browser: ${executable.name}${version ? ' ' + version : ''}`);
console.log(` Install location: ${(_executable$directory = executable.directory) !== null && _executable$directory !== void 0 ? _executable$directory : '<system>'}`);
if ((_executable$downloadU = executable.downloadURLs) !== null && _executable$downloadU !== void 0 && _executable$downloadU.length) {
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]}`);
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 _server.registry.install(executables, forceReinstall);
await _server.registry.validateHostRequirementsForExecutablesIfNeeded(executables, process.env.PW_LANG_NAME || 'javascript').catch(e => {
e.name = 'Playwright Host validation warning';
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\n${e}`);
(0, _utils.gracefullyProcessExitDoNotHang)(1);
console.log(`Failed to install browsers
${e}`);
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
}).addHelpText('afterAll', `
}).addHelpText("afterAll", `
Examples:
- $ install
@@ -136,106 +175,110 @@ Examples:
- $ install chrome firefox
Install custom browsers, supports ${suggestedBrowsersToInstall()}.`);
_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 => {
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 _server.registry.uninstall(!!options.all).then(({
numberOfBrowsersLeft
}) => {
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.\nTo uninstall Playwright browsers for all installations, re-run with --all flag.`);
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);
});
_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) {
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 _server.registry.installDeps(defaultBrowsersToInstall({}), !!options.dryRun);else await _server.registry.installDeps(checkBrowsersToInstall(args, {}), !!options.dryRun);
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\n${e}`);
(0, _utils.gracefullyProcessExitDoNotHang)(1);
console.log(`Failed to install browser dependencies
${e}`);
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
}).addHelpText('afterAll', `
}).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) {
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', `
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) {
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', `
}).addHelpText("afterAll", `
Examples:
$ screenshot -b webkit https://example.com example.png`);
commandWithOpenOptions('pdf <url> <filename>', 'save page as pdf', [['--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) {
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', `
}).addHelpText("afterAll", `
Examples:
$ pdf https://example.com example.pdf`);
_utilsBundle.program.command('run-driver', {
hidden: true
}).action(function (options) {
(0, _driver.runDriver)();
import_utilsBundle.program.command("run-driver", { hidden: true }).action(function(options) {
(0, import_driver.runDriver)();
});
_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, _driver.runServer)({
port: options.port ? +options.port : undefined,
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
extension: options.mode === "extension" || !!process.env.PW_EXTENSION_MODE
}).catch(logErrorAndExit);
});
_utilsBundle.program.command('print-api-json', {
hidden: true
}).action(function (options) {
(0, _driver.printApiJson)();
import_utilsBundle.program.command("print-api-json", { hidden: true }).action(function(options) {
(0, import_driver.printApiJson)();
});
_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, _driver.launchBrowserServer)(options.browser, options.config);
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);
});
_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';
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 !== undefined || options.host !== undefined) (0, _traceViewer.runTraceInBrowser)(traces, openOptions).catch(logErrorAndExit);else (0, _traceViewer.runTraceViewerApp)(traces, options.browser, openOptions, true).catch(logErrorAndExit);
}).addHelpText('afterAll', `
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`);
@@ -243,79 +286,67 @@ async function launchContext(options, extraOptions) {
validateOptions(options);
const browserType = lookupBrowserType(options);
const launchOptions = extraOptions;
if (options.channel) launchOptions.channel = options.channel;
if (options.channel)
launchOptions.channel = options.channel;
launchOptions.handleSIGINT = false;
const contextOptions =
const contextOptions = (
// Copy the device descriptor since we have to compare and modify the options.
options.device ? {
...playwright.devices[options.device]
} : {};
// In headful mode, use host device scale factor for things to look nice.
// In headless, keep things the way it works in Playwright by default.
// Assume high-dpi on MacOS. TODO: this is not perfect.
if (!extraOptions.headless) contextOptions.deviceScaleFactor = _os.default.platform() === 'darwin' ? 2 : 1;
// Work around the WebKit GTK scrolling issue.
if (browserType.name() === 'webkit' && process.platform === 'linux') {
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 = undefined;
if (options.blockServiceWorkers) contextOptions.serviceWorkers = 'block';
// Proxy
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;
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._didSetSourcesForTest = (text) => {
process.stdout.write("\n-------------8<-------------\n");
process.stdout.write(text);
process.stdout.write('\n-------------8<-------------\n');
process.stdout.write("\n-------------8<-------------\n");
const autoExitCondition = process.env.PWTEST_CLI_AUTO_EXIT_WHEN;
if (autoExitCondition && text.includes(autoExitCondition)) closeBrowser();
if (autoExitCondition && text.includes(autoExitCondition))
closeBrowser();
};
// Make sure we exit abnormally when browser crashes.
const logs = [];
require('playwright-core/lib/utilsBundle').debug.log = (...args) => {
const line = require('util').format(...args) + '\n';
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'));
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, _utils.gracefullyProcessExitDoNotHang)(1);
process.stderr.write("Detected browser crash.\n");
(0, import_utils.gracefullyProcessExitDoNotHang)(1);
}
});
}
// Viewport size
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
};
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"');
}
}
// Geolocation
if (options.geolocation) {
try {
const [latitude, longitude] = options.geolocation.split(',').map(n => parseFloat(n.trim()));
const [latitude, longitude] = options.geolocation.split(",").map((n) => parseFloat(n.trim()));
contextOptions.geolocation = {
latitude,
longitude
@@ -323,94 +354,71 @@ async function launchContext(options, extraOptions) {
} catch (e) {
throw new Error('Invalid geolocation format, should be "lat,long". For example --geolocation="37.819722,-122.478611"');
}
contextOptions.permissions = ['geolocation'];
contextOptions.permissions = ["geolocation"];
}
// User agent
if (options.userAgent) contextOptions.userAgent = options.userAgent;
// Lang
if (options.lang) contextOptions.locale = options.lang;
// Color scheme
if (options.colorScheme) contextOptions.colorScheme = options.colorScheme;
// Timezone
if (options.timezone) contextOptions.timezoneId = options.timezone;
// Storage
if (options.loadStorage) contextOptions.storageState = options.loadStorage;
if (options.ignoreHttpsErrors) contextOptions.ignoreHTTPSErrors = true;
// HAR
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: _path.default.resolve(process.cwd(), options.saveHar),
mode: 'minimal'
};
if (options.saveHarGlob) contextOptions.recordHar.urlFilter = options.saveHarGlob;
contextOptions.serviceWorkers = 'block';
contextOptions.recordHar = { path: import_path.default.resolve(process.cwd(), options.saveHar), mode: "minimal" };
if (options.saveHarGlob)
contextOptions.recordHar.urlFilter = options.saveHarGlob;
contextOptions.serviceWorkers = "block";
}
// Close app when the last window closes.
const context = await browser.newContext(contextOptions);
let closingBrowser = false;
async function closeBrowser() {
// We can come here multiple times. For example, saving storage creates
// a temporary page and we call closeBrowser again when that page closes.
if (closingBrowser) return;
if (closingBrowser)
return;
closingBrowser = true;
if (options.saveStorage) await context.storageState({
path: options.saveStorage
}).catch(e => null);
if (options.saveHar) await context.close();
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', () => {}); // Prevent dialogs from being automatically dismissed.
page.on('close', () => {
const hasPage = browser.contexts().some(context => context.pages().length > 0);
if (hasPage) return;
// Avoid the error when the last page is closed because the browser has been closed.
closeBrowser().catch(() => {});
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 () => {
});
process.on("SIGINT", async () => {
await closeBrowser();
(0, _utils.gracefullyProcessExitDoNotHang)(130);
(0, import_utils.gracefullyProcessExitDoNotHang)(130);
});
const timeout = options.timeout ? parseInt(options.timeout, 10) : 0;
context.setDefaultTimeout(timeout);
context.setDefaultNavigationTimeout(timeout);
// Omit options that we add automatically for presentation purpose.
delete launchOptions.headless;
delete launchOptions.executablePath;
delete launchOptions.handleSIGINT;
delete contextOptions.deviceScaleFactor;
return {
browser,
browserName: browserType.name(),
context,
contextOptions,
launchOptions
};
return { browser, browserName: browserType.name(), context, contextOptions, launchOptions };
}
async function openPage(context, url) {
const page = await context.newPage();
if (url) {
if (_fs.default.existsSync(url)) url = 'file://' + _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 && (0, _errors.isTargetClosedError)(error)) {
// Tests with PWTEST_CLI_AUTO_EXIT_WHEN might close page too fast, resulting
// in a stray navigation aborted error. We should ignore it.
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;
}
@@ -419,14 +427,7 @@ async function openPage(context, url) {
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
});
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,
@@ -438,33 +439,23 @@ async function open(options, url, language) {
await openPage(context, url);
}
async function codegen(options, url) {
const {
target: language,
output: outputFile,
testIdAttribute: testIdAttributeName
} = options;
const tracesDir = _path.default.join(_os.default.tmpdir(), `playwright-recorder-trace-${Date.now()}`);
const {
context,
launchOptions,
contextOptions
} = await launchContext(options, {
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
});
_utilsBundle.dotenv.config({
path: 'playwright.env'
});
import_utilsBundle.dotenv.config({ path: "playwright.env" });
await context._enableRecorder({
language,
launchOptions,
contextOptions,
device: options.device,
saveStorage: options.saveStorage,
mode: 'recording',
mode: "recording",
testIdAttributeName,
outputFile: outputFile ? _path.default.resolve(outputFile) : undefined,
outputFile: outputFile ? import_path.default.resolve(outputFile) : void 0,
handleSIGINT: false
});
await openPage(context, url);
@@ -479,41 +470,24 @@ async function waitForPage(page, captureOptions) {
await page.waitForTimeout(parseInt(captureOptions.waitForTimeout, 10));
}
}
async function screenshot(options, captureOptions, url, path) {
const {
context
} = await launchContext(options, {
headless: true
});
console.log('Navigating to ' + url);
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 ' + path);
await page.screenshot({
path,
fullPage: !!captureOptions.fullPage
});
// launchContext takes care of closing the browser.
console.log("Capturing screenshot into " + path2);
await page.screenshot({ path: path2, fullPage: !!captureOptions.fullPage });
await page.close();
}
async function pdf(options, captureOptions, url, path) {
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);
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 ' + path);
await page.pdf({
path
});
// launchContext takes care of closing the browser.
console.log("Saving as pdf into " + path2);
await page.pdf({ path: path2, format: captureOptions.paperFormat });
await page.close();
}
function lookupBrowserType(options) {
@@ -524,60 +498,70 @@ function lookupBrowserType(options) {
}
let browserType;
switch (name) {
case 'chromium':
case "chromium":
browserType = playwright.chromium;
break;
case 'webkit':
case "webkit":
browserType = playwright.webkit;
break;
case 'firefox':
case "firefox":
browserType = playwright.firefox;
break;
case 'cr':
case "cr":
browserType = playwright.chromium;
break;
case 'wk':
case "wk":
browserType = playwright.webkit;
break;
case 'ff':
case "ff":
browserType = playwright.firefox;
break;
}
if (browserType) return browserType;
_utilsBundle.program.help();
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'));
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"');
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, _utils.gracefullyProcessExitDoNotHang)(1);
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';
return process.env.PW_LANG_NAME || "playwright-test";
}
function commandWithOpenOptions(command, description, options) {
let result = _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"');
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':
case "python":
return `playwright`;
case 'java':
case "java":
return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="...options.."`;
case 'csharp':
case "csharp":
return `pwsh bin/Debug/netX/playwright.ps1`;
default:
{
const packageManagerCommand = (0, _utils.getPackageManagerExecCommand)();
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,67 +1,74 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "program", {
enumerable: true,
get: function () {
return _program.program;
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
});
var _utils = require("../utils");
var _program = require("./program");
/**
* 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.
*/
/* eslint-disable no-console */
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']) {
for (const pkg of ["playwright", "playwright-chromium", "playwright-firefox", "playwright-webkit"]) {
try {
require.resolve(pkg);
packages.push(pkg);
} catch (e) {}
} catch (e) {
}
if (!packages.length) packages.push('playwright');
const packageManager = (0, _utils.getPackageManager)();
if (packageManager === 'yarn') {
}
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(` 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');
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');
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']];
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 = _program.program.command(command).allowUnknownOption(true);
const playwrightTest = import_program.program.command(command).allowUnknownOption(true);
playwrightTest.description(`${description} Available in @playwright/test package.`);
playwrightTest.action(async () => {
printPlaywrightTestError(command);
(0, _utils.gracefullyProcessExitDoNotHang)(1);
(0, import_processLauncher.gracefullyProcessExitDoNotHang)(1);
});
}
}
if (!process.env.PW_LANG_NAME) addExternalPlaywrightTestCommands();
if (!process.env.PW_LANG_NAME)
addExternalPlaywrightTestCommands();
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
program
});

View File

@@ -1,33 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Accessibility = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
module.exports = __toCommonJS(accessibility_exports);
function axNodeFromProtocol(axNode) {
const result = {
...axNode,
value: axNode.valueNumber !== undefined ? 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) : undefined
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;
@@ -35,16 +35,15 @@ function axNodeFromProtocol(axNode) {
}
class Accessibility {
constructor(channel) {
this._channel = void 0;
this._channel = channel;
}
async snapshot(options = {}) {
const root = options.root ? options.root._elementChannel : undefined;
const result = await this._channel.accessibilitySnapshot({
interestingOnly: options.interestingOnly,
root
});
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;
}
}
exports.Accessibility = Accessibility;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Accessibility
});

View File

@@ -1,161 +1,125 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.AndroidWebView = exports.AndroidSocket = exports.AndroidInput = exports.AndroidDevice = exports.Android = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _utils = require("../utils");
var _events = require("./events");
var _browserContext = require("./browserContext");
var _channelOwner = require("./channelOwner");
var _timeoutSettings = require("../common/timeoutSettings");
var _waiter = require("./waiter");
var _events2 = require("events");
var _connection = require("./connection");
var _errors = require("./errors");
var _timeoutRunner = require("../utils/timeoutRunner");
let _Symbol$asyncDispose;
/**
* 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.
*/
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class Android extends _channelOwner.ChannelOwner {
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 = void 0;
this._serverLauncher = void 0;
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
this._timeoutSettings = new import_timeoutSettings.TimeoutSettings(this._platform);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._channel.setDefaultTimeoutNoReply({
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));
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');
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, _utils.monotonicTime)() + options.timeout : 0;
const headers = {
'x-playwright-browser': 'android',
...options.headers
};
const localUtils = this._connection.localUtils();
const connectParams = {
wsEndpoint,
headers,
slowMo: options.slowMo,
timeout: options.timeout
};
const {
pipe
} = await localUtils._channel.connect(connectParams);
const closePipe = () => pipe.close().catch(() => {});
const connection = new _connection.Connection(localUtils, this._instrumentation);
connection.markAsRemote();
connection.on('close', closePipe);
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;
let closeError;
const onPipeClosed = () => {
var _device;
(_device = device) === null || _device === void 0 || _device._didClose();
connection.close(closeError);
};
pipe.on('closed', onPipeClosed);
connection.onmessage = message => pipe.send({
message
}).catch(onPipeClosed);
pipe.on('message', ({
message
}) => {
try {
connection.dispatch(message);
} catch (e) {
closeError = String(e);
closePipe();
}
connection.on("close", () => {
device?._didClose();
});
const result = await (0, _timeoutRunner.raceAgainstDeadline)(async () => {
const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
const playwright = await connection.initializePlaywright();
if (!playwright._initializer.preConnectedAndroidDevice) {
closePipe();
throw new Error('Malformed endpoint. Did you use Android.launchServer method?');
connection.close();
throw new Error("Malformed endpoint. Did you use Android.launchServer method?");
}
device = AndroidDevice.from(playwright._initializer.preConnectedAndroidDevice);
device._shouldCloseConnectionOnClose = true;
device.on(_events.Events.AndroidDevice.Close, closePipe);
device.on(import_events.Events.AndroidDevice.Close, () => connection.close());
return device;
}, deadline);
if (!result.timedOut) {
return result.result;
} else {
closePipe();
connection.close();
throw new Error(`Timeout ${options.timeout}ms exceeded`);
}
});
}
}
exports.Android = Android;
_Symbol$asyncDispose = Symbol.asyncDispose;
class AndroidDevice extends _channelOwner.ChannelOwner {
static from(androidDevice) {
return androidDevice._object;
}
class AndroidDevice extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._timeoutSettings = void 0;
this._webViews = new Map();
this._webViews = /* @__PURE__ */ new Map();
this._shouldCloseConnectionOnClose = false;
this.input = void 0;
this.input = new AndroidInput(this);
this._timeoutSettings = new _timeoutSettings.TimeoutSettings(parent._timeoutSettings);
this._channel.on('webViewAdded', ({
webView
}) => this._onWebViewAdded(webView));
this._channel.on('webViewRemoved', ({
socketName
}) => this._onWebViewRemoved(socketName));
this._channel.on('close', () => this._didClose());
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(_events.Events.AndroidDevice.WebView, 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(_events.Events.AndroidWebView.Close);
if (view)
view.emit(import_events.Events.AndroidWebView.Close);
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._channel.setDefaultTimeoutNoReply({
timeout
this._wrapApiCall(async () => {
await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true).catch(() => {
});
}
serial() {
@@ -168,179 +132,124 @@ class AndroidDevice extends _channelOwner.ChannelOwner {
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;
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
});
if (webView)
return webView;
return await this.waitForEvent("webview", { ...options, predicate });
}
async wait(selector, options) {
await this._channel.wait({
selector: toSelectorChannel(selector),
...options
});
await this._channel.wait({ selector: toSelectorChannel(selector), ...options });
}
async fill(selector, text, options) {
await this._channel.fill({
selector: toSelectorChannel(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
});
await this._channel.tap({ selector: toSelectorChannel(selector), ...options });
}
async drag(selector, dest, options) {
await this._channel.drag({
selector: toSelectorChannel(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
});
await this._channel.fling({ selector: toSelectorChannel(selector), direction, ...options });
}
async longTap(selector, options) {
await this._channel.longTap({
selector: toSelectorChannel(selector),
...options
});
await this._channel.longTap({ selector: toSelectorChannel(selector), ...options });
}
async pinchClose(selector, percent, options) {
await this._channel.pinchClose({
selector: toSelectorChannel(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
});
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
});
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
});
await this._channel.swipe({ selector: toSelectorChannel(selector), direction, percent, ...options });
}
async info(selector) {
return (await this._channel.info({
selector: toSelectorChannel(selector)
})).info;
return (await this._channel.info({ selector: toSelectorChannel(selector) })).info;
}
async screenshot(options = {}) {
const {
binary
} = await this._channel.screenshot();
if (options.path) await _fs.default.promises.writeFile(options.path, binary);
const { binary } = await this._channel.screenshot();
if (options.path)
await this._platform.fs().promises.writeFile(options.path, binary);
return binary;
}
async [_Symbol$asyncDispose]() {
async [Symbol.asyncDispose]() {
await this.close();
}
async close() {
try {
if (this._shouldCloseConnectionOnClose) this._connection.close();else await this._channel.close();
if (this._shouldCloseConnectionOnClose)
this._connection.close();
else
await this._channel.close();
} catch (e) {
if ((0, _errors.isTargetClosedError)(e)) return;
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
_didClose() {
this.emit(_events.Events.AndroidDevice.Close, this);
this.emit(import_events.Events.AndroidDevice.Close, this);
}
async shell(command) {
const {
result
} = await this._channel.shell({
command
});
const { result } = await this._channel.shell({ command });
return result;
}
async open(command) {
return AndroidSocket.from((await this._channel.open({
command
})).socket);
return AndroidSocket.from((await this._channel.open({ command })).socket);
}
async installApk(file, options) {
await this._channel.installApk({
file: await loadFile(file),
args: options && options.args
});
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(file),
path,
mode: options ? options.mode : undefined
});
await this._channel.push({ file: await loadFile(this._platform, file), path, mode: options ? options.mode : void 0 });
}
async launchBrowser(options = {}) {
const contextOptions = await (0, _browserContext.prepareBrowserContextParams)(options);
const contextOptions = await (0, import_browserContext.prepareBrowserContextParams)(this._platform, options);
const result = await this._channel.launchBrowser(contextOptions);
const context = _browserContext.BrowserContext.from(result.context);
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 = _waiter.Waiter.createForEvent(this, event);
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 !== _events.Events.AndroidDevice.Close) waiter.rejectOnEvent(this, _events.Events.AndroidDevice.Close, () => new _errors.TargetClosedError());
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;
});
}
}
exports.AndroidDevice = AndroidDevice;
class AndroidSocket extends _channelOwner.ChannelOwner {
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(_events.Events.AndroidSocket.Data, data));
this._channel.on('close', () => this.emit(_events.Events.AndroidSocket.Close));
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
});
await this._channel.write({ data });
}
async close() {
await this._channel.close();
@@ -349,46 +258,31 @@ class AndroidSocket extends _channelOwner.ChannelOwner {
await this.close();
}
}
exports.AndroidSocket = AndroidSocket;
async function loadFile(file) {
if ((0, _utils.isString)(file)) return await _fs.default.promises.readFile(file);
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 = void 0;
this._device = device;
}
async type(text) {
await this._device._channel.inputType({
text
});
await this._device._channel.inputType({ text });
}
async press(key) {
await this._device._channel.inputPress({
key
});
await this._device._channel.inputPress({ key });
}
async tap(point) {
await this._device._channel.inputTap({
point
});
await this._device._channel.inputTap({ point });
}
async swipe(from, segments, steps) {
await this._device._channel.inputSwipe({
segments,
steps
});
await this._device._channel.inputSwipe({ segments, steps });
}
async drag(from, to, steps) {
await this._device._channel.inputDrag({
from,
to,
steps
});
await this._device._channel.inputDrag({ from, to, steps });
}
}
exports.AndroidInput = AndroidInput;
function toSelectorChannel(selector) {
const {
checkable,
@@ -409,10 +303,12 @@ function toSelectorChannel(selector) {
selected,
text
} = selector;
const toRegex = value => {
if (value === undefined) return undefined;
if ((0, _utils.isRegExp)(value)) return value.source;
return '^' + value.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d') + '$';
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,
@@ -427,24 +323,16 @@ function toSelectorChannel(selector) {
enabled,
focusable,
focused,
hasChild: hasChild ? {
selector: toSelectorChannel(hasChild.selector)
} : undefined,
hasDescendant: hasDescendant ? {
selector: toSelectorChannel(hasDescendant.selector),
maxDepth: hasDescendant.maxDepth
} : undefined,
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 _events2.EventEmitter {
class AndroidWebView extends import_eventEmitter.EventEmitter {
constructor(device, data) {
super();
this._device = void 0;
this._data = void 0;
this._pagePromise = void 0;
super(device._platform);
this._device = device;
this._data = data;
}
@@ -458,16 +346,20 @@ class AndroidWebView extends _events2.EventEmitter {
return this._data.socketName;
}
async page() {
if (!this._pagePromise) this._pagePromise = this._fetchPage();
if (!this._pagePromise)
this._pagePromise = this._fetchPage();
return await this._pagePromise;
}
async _fetchPage() {
const {
context
} = await this._device._channel.connectToWebView({
socketName: this._data.socketName
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
});
return _browserContext.BrowserContext.from(context).pages()[0];
}
}
exports.AndroidWebView = AndroidWebView;

View File

@@ -1,285 +1,137 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "APIRequest", {
enumerable: true,
get: function () {
return _fetch.APIRequest;
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
});
Object.defineProperty(exports, "APIRequestContext", {
enumerable: true,
get: function () {
return _fetch.APIRequestContext;
}
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
});
Object.defineProperty(exports, "APIResponse", {
enumerable: true,
get: function () {
return _fetch.APIResponse;
}
});
Object.defineProperty(exports, "Accessibility", {
enumerable: true,
get: function () {
return _accessibility.Accessibility;
}
});
Object.defineProperty(exports, "Android", {
enumerable: true,
get: function () {
return _android.Android;
}
});
Object.defineProperty(exports, "AndroidDevice", {
enumerable: true,
get: function () {
return _android.AndroidDevice;
}
});
Object.defineProperty(exports, "AndroidInput", {
enumerable: true,
get: function () {
return _android.AndroidInput;
}
});
Object.defineProperty(exports, "AndroidSocket", {
enumerable: true,
get: function () {
return _android.AndroidSocket;
}
});
Object.defineProperty(exports, "AndroidWebView", {
enumerable: true,
get: function () {
return _android.AndroidWebView;
}
});
Object.defineProperty(exports, "Browser", {
enumerable: true,
get: function () {
return _browser.Browser;
}
});
Object.defineProperty(exports, "BrowserContext", {
enumerable: true,
get: function () {
return _browserContext.BrowserContext;
}
});
Object.defineProperty(exports, "BrowserType", {
enumerable: true,
get: function () {
return _browserType.BrowserType;
}
});
Object.defineProperty(exports, "CDPSession", {
enumerable: true,
get: function () {
return _cdpSession.CDPSession;
}
});
Object.defineProperty(exports, "Clock", {
enumerable: true,
get: function () {
return _clock.Clock;
}
});
Object.defineProperty(exports, "ConsoleMessage", {
enumerable: true,
get: function () {
return _consoleMessage.ConsoleMessage;
}
});
Object.defineProperty(exports, "Coverage", {
enumerable: true,
get: function () {
return _coverage.Coverage;
}
});
Object.defineProperty(exports, "Dialog", {
enumerable: true,
get: function () {
return _dialog.Dialog;
}
});
Object.defineProperty(exports, "Download", {
enumerable: true,
get: function () {
return _download.Download;
}
});
Object.defineProperty(exports, "Electron", {
enumerable: true,
get: function () {
return _electron.Electron;
}
});
Object.defineProperty(exports, "ElectronApplication", {
enumerable: true,
get: function () {
return _electron.ElectronApplication;
}
});
Object.defineProperty(exports, "ElementHandle", {
enumerable: true,
get: function () {
return _elementHandle.ElementHandle;
}
});
Object.defineProperty(exports, "FileChooser", {
enumerable: true,
get: function () {
return _fileChooser.FileChooser;
}
});
Object.defineProperty(exports, "Frame", {
enumerable: true,
get: function () {
return _frame.Frame;
}
});
Object.defineProperty(exports, "FrameLocator", {
enumerable: true,
get: function () {
return _locator.FrameLocator;
}
});
Object.defineProperty(exports, "JSHandle", {
enumerable: true,
get: function () {
return _jsHandle.JSHandle;
}
});
Object.defineProperty(exports, "Keyboard", {
enumerable: true,
get: function () {
return _input.Keyboard;
}
});
Object.defineProperty(exports, "Locator", {
enumerable: true,
get: function () {
return _locator.Locator;
}
});
Object.defineProperty(exports, "Mouse", {
enumerable: true,
get: function () {
return _input.Mouse;
}
});
Object.defineProperty(exports, "Page", {
enumerable: true,
get: function () {
return _page.Page;
}
});
Object.defineProperty(exports, "Playwright", {
enumerable: true,
get: function () {
return _playwright.Playwright;
}
});
Object.defineProperty(exports, "Request", {
enumerable: true,
get: function () {
return _network.Request;
}
});
Object.defineProperty(exports, "Response", {
enumerable: true,
get: function () {
return _network.Response;
}
});
Object.defineProperty(exports, "Route", {
enumerable: true,
get: function () {
return _network.Route;
}
});
Object.defineProperty(exports, "Selectors", {
enumerable: true,
get: function () {
return _selectors.Selectors;
}
});
Object.defineProperty(exports, "TimeoutError", {
enumerable: true,
get: function () {
return _errors.TimeoutError;
}
});
Object.defineProperty(exports, "Touchscreen", {
enumerable: true,
get: function () {
return _input.Touchscreen;
}
});
Object.defineProperty(exports, "Tracing", {
enumerable: true,
get: function () {
return _tracing.Tracing;
}
});
Object.defineProperty(exports, "Video", {
enumerable: true,
get: function () {
return _video.Video;
}
});
Object.defineProperty(exports, "WebError", {
enumerable: true,
get: function () {
return _webError.WebError;
}
});
Object.defineProperty(exports, "WebSocket", {
enumerable: true,
get: function () {
return _network.WebSocket;
}
});
Object.defineProperty(exports, "WebSocketRoute", {
enumerable: true,
get: function () {
return _network.WebSocketRoute;
}
});
Object.defineProperty(exports, "Worker", {
enumerable: true,
get: function () {
return _worker.Worker;
}
});
var _accessibility = require("./accessibility");
var _android = require("./android");
var _browser = require("./browser");
var _browserContext = require("./browserContext");
var _browserType = require("./browserType");
var _clock = require("./clock");
var _consoleMessage = require("./consoleMessage");
var _coverage = require("./coverage");
var _dialog = require("./dialog");
var _download = require("./download");
var _electron = require("./electron");
var _locator = require("./locator");
var _elementHandle = require("./elementHandle");
var _fileChooser = require("./fileChooser");
var _errors = require("./errors");
var _frame = require("./frame");
var _input = require("./input");
var _jsHandle = require("./jsHandle");
var _network = require("./network");
var _fetch = require("./fetch");
var _page = require("./page");
var _selectors = require("./selectors");
var _tracing = require("./tracing");
var _video = require("./video");
var _worker = require("./worker");
var _cdpSession = require("./cdpSession");
var _playwright = require("./playwright");
var _webError = require("./webError");

View File

@@ -1,51 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Artifact = void 0;
var fs = _interopRequireWildcard(require("fs"));
var _stream = require("./stream");
var _fileUtils = require("../utils/fileUtils");
var _channelOwner = require("./channelOwner");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
/**
* 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.
*/
class Artifact extends _channelOwner.ChannelOwner {
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.`);
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
});
await this._channel.saveAs({ path });
return;
}
const result = await this._channel.saveAsStream();
const stream = _stream.Stream.from(result.stream);
await (0, _fileUtils.mkdirIfNeeded)(path);
const stream = import_stream.Stream.from(result.stream);
await (0, import_fileUtils.mkdirIfNeeded)(this._platform, path);
await new Promise((resolve, reject) => {
stream.stream().pipe(fs.createWriteStream(path)).on('finish', resolve).on('error', reject);
stream.stream().pipe(this._platform.fs().createWriteStream(path)).on("finish", resolve).on("error", reject);
});
}
async failure() {
@@ -53,20 +50,20 @@ class Artifact extends _channelOwner.ChannelOwner {
}
async createReadStream() {
const result = await this._channel.stream();
const stream = _stream.Stream.from(result.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 => {
stream.on("data", (chunk) => {
chunks.push(chunk);
});
stream.on('end', () => {
stream.on("end", () => {
resolve(Buffer.concat(chunks));
});
stream.on('error', reject);
stream.on("error", reject);
});
}
async cancel() {
@@ -76,4 +73,7 @@ class Artifact extends _channelOwner.ChannelOwner {
return await this._channel.delete();
}
}
exports.Artifact = Artifact;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Artifact
});

View File

@@ -1,55 +1,46 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Browser = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _browserContext = require("./browserContext");
var _channelOwner = require("./channelOwner");
var _events = require("./events");
var _errors = require("./errors");
var _cdpSession = require("./cdpSession");
var _artifact = require("./artifact");
var _utils = require("../utils");
let _Symbol$asyncDispose;
/**
* 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.
*/
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
_Symbol$asyncDispose = Symbol.asyncDispose;
class Browser extends _channelOwner.ChannelOwner {
static from(browser) {
return browser._object;
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 = new Set();
this._contexts = /* @__PURE__ */ new Set();
this._isConnected = true;
this._closedPromise = void 0;
this._shouldCloseConnectionOnClose = false;
this._browserType = void 0;
this._options = {};
this._name = void 0;
this._path = void 0;
// Used from @playwright/test fixtures.
this._connectHeaders = void 0;
this._closeReason = void 0;
this._name = initializer.name;
this._channel.on('close', () => this._didClose());
this._closedPromise = new Promise(f => this.once(_events.Events.Browser.Disconnected, f));
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;
@@ -61,7 +52,8 @@ class Browser extends _channelOwner.ChannelOwner {
return await this._wrapApiCall(async () => {
for (const context of this._contexts) {
await this._browserType._willCloseContext(context);
for (const page of context.pages()) page._onClose();
for (const page of context.pages())
page._onClose();
context._onClose();
}
return await this._innerNewContext(options, true);
@@ -69,19 +61,14 @@ class Browser extends _channelOwner.ChannelOwner {
}
async _stopPendingOperations(reason) {
return await this._wrapApiCall(async () => {
await this._channel.stopPendingOperations({
reason
});
await this._channel.stopPendingOperations({ reason });
}, true);
}
async _innerNewContext(options = {}, forReuse) {
options = {
...this._browserType._defaultContextOptions,
...options
};
const contextOptions = await (0, _browserContext.prepareBrowserContextParams)(options);
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 = _browserContext.BrowserContext.from(response.context);
const context = import_browserContext.BrowserContext.from(response.context);
await this._browserType._didCreateContext(context, contextOptions, this._options, options.logger || this._logger);
return context;
}
@@ -104,42 +91,46 @@ class Browser extends _channelOwner.ChannelOwner {
return this._isConnected;
}
async newBrowserCDPSession() {
return _cdpSession.CDPSession.from((await this._channel.newBrowserCDPSession()).session);
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 : undefined
});
await this._channel.startTracing({ ...options, page: page ? page._channel : void 0 });
}
async stopTracing() {
const artifact = _artifact.Artifact.from((await this._channel.stopTracing()).artifact);
const artifact = import_artifact.Artifact.from((await this._channel.stopTracing()).artifact);
const buffer = await artifact.readIntoBuffer();
await artifact.delete();
if (this._path) {
await (0, _utils.mkdirIfNeeded)(this._path);
await _fs.default.promises.writeFile(this._path, buffer);
this._path = undefined;
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]() {
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);
if (this._shouldCloseConnectionOnClose)
this._connection.close();
else
await this._channel.close(options);
await this._closedPromise;
} catch (e) {
if ((0, _errors.isTargetClosedError)(e)) return;
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
_didClose() {
this._isConnected = false;
this.emit(_events.Events.Browser.Disconnected, this);
this.emit(import_events.Events.Browser.Disconnected, this);
}
}
exports.Browser = Browser;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Browser
});

View File

@@ -1,250 +1,244 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.BrowserContext = void 0;
exports.prepareBrowserContextParams = prepareBrowserContextParams;
exports.toClientCertificatesProtocol = toClientCertificatesProtocol;
var _page = require("./page");
var _frame = require("./frame");
var network = _interopRequireWildcard(require("./network"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var _channelOwner = require("./channelOwner");
var _clientHelper = require("./clientHelper");
var _browser = require("./browser");
var _worker = require("./worker");
var _events = require("./events");
var _timeoutSettings = require("../common/timeoutSettings");
var _waiter = require("./waiter");
var _utils = require("../utils");
var _cdpSession = require("./cdpSession");
var _tracing = require("./tracing");
var _artifact = require("./artifact");
var _fetch = require("./fetch");
var _stackTrace = require("../utils/stackTrace");
var _harRouter = require("./harRouter");
var _consoleMessage = require("./consoleMessage");
var _dialog = require("./dialog");
var _webError = require("./webError");
var _errors = require("./errors");
var _clock = require("./clock");
let _Symbol$asyncDispose;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
_Symbol$asyncDispose = Symbol.asyncDispose;
class BrowserContext extends _channelOwner.ChannelOwner {
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;
}
constructor(parent, type, guid, initializer) {
var _this$_browser, _this$_browser2;
super(parent, type, guid, initializer);
this._pages = new Set();
this._routes = [];
this._webSocketRoutes = [];
this._browser = null;
this._browserType = void 0;
this._bindings = new Map();
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
this._ownerPage = void 0;
this._closedPromise = void 0;
this._options = {};
this.request = void 0;
this.tracing = void 0;
this.clock = void 0;
this._backgroundPages = new Set();
this._serviceWorkers = new Set();
this._isChromium = void 0;
this._harRecorders = new Map();
this._closeWasCalled = false;
this._closeReason = void 0;
this._harRouters = [];
if (parent instanceof _browser.Browser) this._browser = parent;
(_this$_browser = this._browser) === null || _this$_browser === void 0 || _this$_browser._contexts.add(this);
this._isChromium = ((_this$_browser2 = this._browser) === null || _this$_browser2 === void 0 ? void 0 : _this$_browser2._name) === 'chromium';
this.tracing = _tracing.Tracing.from(initializer.tracing);
this.request = _fetch.APIRequestContext.from(initializer.requestContext);
this.clock = new _clock.Clock(this);
this._channel.on('bindingCall', ({
binding
}) => this._onBinding(_page.BindingCall.from(binding)));
this._channel.on('close', () => this._onClose());
this._channel.on('page', ({
page
}) => this._onPage(_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 = _page.Page.from(page);
this._backgroundPages.add(backgroundPage);
this.emit(_events.Events.BrowserContext.BackgroundPage, backgroundPage);
});
this._channel.on('serviceWorker', ({
worker
}) => {
const serviceWorker = _worker.Worker.from(worker);
serviceWorker._context = this;
this._serviceWorkers.add(serviceWorker);
this.emit(_events.Events.BrowserContext.ServiceWorker, serviceWorker);
});
this._channel.on('console', event => {
const consoleMessage = new _consoleMessage.ConsoleMessage(event);
this.emit(_events.Events.BrowserContext.Console, consoleMessage);
const page = consoleMessage.page();
if (page) page.emit(_events.Events.Page.Console, consoleMessage);
});
this._channel.on('pageError', ({
error,
page
}) => {
const pageObject = _page.Page.from(page);
const parsedError = (0, _errors.parseError)(error);
this.emit(_events.Events.BrowserContext.WebError, new _webError.WebError(pageObject, parsedError));
if (pageObject) pageObject.emit(_events.Events.Page.PageError, parsedError);
});
this._channel.on('dialog', ({
dialog
}) => {
const dialogObject = _dialog.Dialog.from(dialog);
let hasListeners = this.emit(_events.Events.BrowserContext.Dialog, dialogObject);
const page = dialogObject.page();
if (page) hasListeners = page.emit(_events.Events.Page.Dialog, dialogObject) || hasListeners;
if (!hasListeners) {
// Although we do similar handling on the server side, we still need this logic
// on the client side due to a possible race condition between two async calls:
// a) removing "dialog" listener subscription (client->server)
// b) actual "dialog" event (server->client)
if (dialogObject.type() === 'beforeunload') dialog.accept({}).catch(() => {});else dialog.dismiss().catch(() => {});
}
});
this._channel.on('request', ({
request,
page
}) => this._onRequest(network.Request.from(request), _page.Page.fromNullable(page)));
this._channel.on('requestFailed', ({
request,
failureText,
responseEndTiming,
page
}) => this._onRequestFailed(network.Request.from(request), responseEndTiming, failureText, _page.Page.fromNullable(page)));
this._channel.on('requestFinished', params => this._onRequestFinished(params));
this._channel.on('response', ({
response,
page
}) => this._onResponse(network.Response.from(response), _page.Page.fromNullable(page)));
this._closedPromise = new Promise(f => this.once(_events.Events.BrowserContext.Close, f));
this._setEventToSubscriptionMapping(new Map([[_events.Events.BrowserContext.Console, 'console'], [_events.Events.BrowserContext.Dialog, 'dialog'], [_events.Events.BrowserContext.Request, 'request'], [_events.Events.BrowserContext.Response, 'response'], [_events.Events.BrowserContext.RequestFinished, 'requestFinished'], [_events.Events.BrowserContext.RequestFailed, 'requestFailed']]));
}
_setOptions(contextOptions, browserOptions) {
this._options = contextOptions;
if (this._options.recordHar) this._harRecorders.set('', {
path: this._options.recordHar.path,
content: this._options.recordHar.content
});
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(_events.Events.BrowserContext.Page, page);
if (page._opener && !page._opener.isClosed()) page._opener.emit(_events.Events.Page.Popup, 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(_events.Events.BrowserContext.Request, request);
if (page) page.emit(_events.Events.Page.Request, request);
this.emit(import_events.Events.BrowserContext.Request, request);
if (page)
page.emit(import_events.Events.Page.Request, request);
}
_onResponse(response, page) {
this.emit(_events.Events.BrowserContext.Response, response);
if (page) page.emit(_events.Events.Page.Response, response);
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(_events.Events.BrowserContext.RequestFailed, request);
if (page) page.emit(_events.Events.Page.RequestFailed, request);
this.emit(import_events.Events.BrowserContext.RequestFailed, request);
if (page)
page.emit(import_events.Events.Page.RequestFailed, request);
}
_onRequestFinished(params) {
const {
responseEndTiming
} = params;
const { responseEndTiming } = params;
const request = network.Request.from(params.request);
const response = network.Response.fromNullable(params.response);
const page = _page.Page.fromNullable(params.page);
const page = import_page.Page.fromNullable(params.page);
request._setResponseEndTiming(responseEndTiming);
this.emit(_events.Events.BrowserContext.RequestFinished, request);
if (page) page.emit(_events.Events.Page.RequestFinished, request);
if (response) response._finishedPromise.resolve(null);
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 the page or the context was closed we stall all requests right away.
if (page !== null && page !== void 0 && page._closeWasCalled || this._closeWasCalled) return;
if (!routeHandler.matches(route.request().url())) continue;
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);
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;
if (!this._routes.length)
this._wrapApiCall(() => this._updateInterceptionPatterns(), true).catch(() => {
});
if (handled)
return;
}
// If the page is closed or unrouteAll() was called without waiting and interception disabled,
// the method will throw an error - silence it.
await route._innerContinue(true /* isFallback */).catch(() => {});
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();
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;
if (!func)
return;
await bindingCall.call(func);
}
setDefaultNavigationTimeout(timeout) {
this._timeoutSettings.setDefaultNavigationTimeout(timeout);
this._wrapApiCall(async () => {
this._channel.setDefaultNavigationTimeoutNoReply({
timeout
}).catch(() => {});
}, true);
await this._channel.setDefaultNavigationTimeoutNoReply({ timeout });
}, true).catch(() => {
});
}
setDefaultTimeout(timeout) {
this._timeoutSettings.setDefaultTimeout(timeout);
this._wrapApiCall(async () => {
this._channel.setDefaultTimeoutNoReply({
timeout
}).catch(() => {});
}, true);
await this._channel.setDefaultTimeoutNoReply({ timeout });
}, true).catch(() => {
});
}
browser() {
return this._browser;
@@ -253,86 +247,67 @@ class BrowserContext extends _channelOwner.ChannelOwner {
return [...this._pages];
}
async newPage() {
if (this._ownerPage) throw new Error('Please use browser.newContext()');
return _page.Page.from((await this._channel.newPage()).page);
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: urls
})).cookies;
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
});
await this._channel.addCookies({ cookies });
}
async clearCookies(options = {}) {
await this._channel.clearCookies({
name: (0, _utils.isString)(options.name) ? options.name : undefined,
nameRegexSource: (0, _utils.isRegExp)(options.name) ? options.name.source : undefined,
nameRegexFlags: (0, _utils.isRegExp)(options.name) ? options.name.flags : undefined,
domain: (0, _utils.isString)(options.domain) ? options.domain : undefined,
domainRegexSource: (0, _utils.isRegExp)(options.domain) ? options.domain.source : undefined,
domainRegexFlags: (0, _utils.isRegExp)(options.domain) ? options.domain.flags : undefined,
path: (0, _utils.isString)(options.path) ? options.path : undefined,
pathRegexSource: (0, _utils.isRegExp)(options.path) ? options.path.source : undefined,
pathRegexFlags: (0, _utils.isRegExp)(options.path) ? options.path.flags : undefined
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
});
await this._channel.grantPermissions({ permissions, ...options });
}
async clearPermissions() {
await this._channel.clearPermissions();
}
async setGeolocation(geolocation) {
await this._channel.setGeolocation({
geolocation: geolocation || undefined
});
await this._channel.setGeolocation({ geolocation: geolocation || void 0 });
}
async setExtraHTTPHeaders(headers) {
network.validateHeaders(headers);
await this._channel.setExtraHTTPHeaders({
headers: (0, _utils.headersObjectToArray)(headers)
});
await this._channel.setExtraHTTPHeaders({ headers: (0, import_headers.headersObjectToArray)(headers) });
}
async setOffline(offline) {
await this._channel.setOffline({
offline
});
await this._channel.setOffline({ offline });
}
async setHTTPCredentials(httpCredentials) {
await this._channel.setHTTPCredentials({
httpCredentials: httpCredentials || undefined
});
await this._channel.setHTTPCredentials({ httpCredentials: httpCredentials || void 0 });
}
async addInitScript(script, arg) {
const source = await (0, _clientHelper.evaluationScript)(script, arg);
await this._channel.addInitScript({
source
});
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
});
await this._channel.exposeBinding({ name, needsHandle: options.handle });
this._bindings.set(name, callback);
}
async exposeFunction(name, callback) {
await this._channel.exposeBinding({
name
});
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._options.baseURL, url, handler, options.times));
this._routes.unshift(new network.RouteHandler(this._platform, this._options.baseURL, url, handler, options.times));
await this._updateInterceptionPatterns();
}
async routeWebSocket(url, handler) {
@@ -340,90 +315,85 @@ class BrowserContext extends _channelOwner.ChannelOwner {
await this._updateWebSocketInterceptionPatterns();
}
async _recordIntoHAR(har, page, options = {}) {
var _options$updateConten, _options$updateMode, _options$updateConten2;
const {
harId
} = await this._channel.harStart({
page: page === null || page === void 0 ? void 0 : page._channel,
const { harId } = await this._channel.harStart({
page: page?._channel,
options: prepareRecordHarOptions({
path: har,
content: (_options$updateConten = options.updateContent) !== null && _options$updateConten !== void 0 ? _options$updateConten : 'attach',
mode: (_options$updateMode = options.updateMode) !== null && _options$updateMode !== void 0 ? _options$updateMode : 'minimal',
content: options.updateContent ?? "attach",
mode: options.updateMode ?? "minimal",
urlFilter: options.url
})
});
this._harRecorders.set(harId, {
path: har,
content: (_options$updateConten2 = options.updateContent) !== null && _options$updateConten2 !== void 0 ? _options$updateConten2 : 'attach'
});
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 _harRouter.HarRouter.create(this._connection.localUtils(), har, options.notFound || 'abort', {
urlMatch: options.url
});
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.forEach((router) => router.dispose());
this._harRouters = [];
}
async unrouteAll(options) {
await this._unrouteInternal(this._routes, [], options === null || options === void 0 ? void 0 : options.behavior);
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, _utils.urlMatchesEqual)(route.url, url) && (!handler || route.handler === handler)) removed.push(route);else remaining.push(route);
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');
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));
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
});
await this._channel.setNetworkInterceptionPatterns({ patterns });
}
async _updateWebSocketInterceptionPatterns() {
const patterns = network.WebSocketRouteHandler.prepareInterceptionPatterns(this._webSocketRoutes);
await this._channel.setWebSocketInterceptionPatterns({
patterns
});
await this._channel.setWebSocketInterceptionPatterns({ patterns });
}
_effectiveCloseReason() {
var _this$_browser3;
return this._closeReason || ((_this$_browser3 = this._browser) === null || _this$_browser3 === void 0 ? void 0 : _this$_browser3._closeReason);
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 = _waiter.Waiter.createForEvent(this, event);
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 !== _events.Events.BrowserContext.Close) waiter.rejectOnEvent(this, _events.Events.BrowserContext.Close, () => new _errors.TargetClosedError(this._effectiveCloseReason()));
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();
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) {
await (0, _utils.mkdirIfNeeded)(options.path);
await _fs.default.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
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;
}
@@ -434,50 +404,43 @@ class BrowserContext extends _channelOwner.ChannelOwner {
return [...this._serviceWorkers];
}
async newCDPSession(page) {
// channelOwner.ts's validation messages don't handle the pseudo-union type, so we're explicit here
if (!(page instanceof _page.Page) && !(page instanceof _frame.Frame)) throw new Error('page: expected Page or Frame');
const result = await this._channel.newCDPSession(page instanceof _page.Page ? {
page: page._channel
} : {
frame: page._channel
});
return _cdpSession.CDPSession.from(result.session);
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() {
var _this$_browserType;
if (this._browser) this._browser._contexts.delete(this);
(_this$_browserType = this._browserType) === null || _this$_browserType === void 0 || (_this$_browserType = _this$_browserType._contexts) === null || _this$_browserType === void 0 || _this$_browserType.delete(this);
if (this._browser)
this._browser._contexts.delete(this);
this._browserType?._contexts?.delete(this);
this._disposeHarRouters();
this.tracing._resetStackCounter();
this.emit(_events.Events.BrowserContext.Close, this);
this.emit(import_events.Events.BrowserContext.Close, this);
}
async [_Symbol$asyncDispose]() {
async [Symbol.asyncDispose]() {
await this.close();
}
async close(options = {}) {
if (this._closeWasCalled) return;
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 () => {
var _this$_browserType2;
await ((_this$_browserType2 = this._browserType) === null || _this$_browserType2 === void 0 ? void 0 : _this$_browserType2._willCloseContext(this));
await this._browserType?._willCloseContext(this);
for (const [harId, harParams] of this._harRecorders) {
const har = await this._channel.harExport({
harId
});
const artifact = _artifact.Artifact.from(har.artifact);
// Server side will compress artifact if content is attach or if file is .zip.
const isCompressed = harParams.content === 'attach' || harParams.path.endsWith('.zip');
const needCompressed = harParams.path.endsWith('.zip');
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) {
await artifact.saveAs(harParams.path + '.tmp');
await this._connection.localUtils()._channel.harUnzip({
zipFile: harParams.path + '.tmp',
harFile: harParams.path
});
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);
}
@@ -491,43 +454,48 @@ class BrowserContext extends _channelOwner.ChannelOwner {
await this._channel.enableRecorder(params);
}
}
exports.BrowserContext = BrowserContext;
async function prepareStorageState(options) {
if (typeof options.storageState !== 'string') return options.storageState;
async function prepareStorageState(platform, options) {
if (typeof options.storageState !== "string")
return options.storageState;
try {
return JSON.parse(await _fs.default.promises.readFile(options.storageState, 'utf8'));
return JSON.parse(await platform.fs().promises.readFile(options.storageState, "utf8"));
} catch (e) {
(0, _stackTrace.rewriteErrorMessage)(e, `Error reading storage state from ${options.storageState}:\n` + e.message);
(0, import_stackTrace.rewriteErrorMessage)(e, `Error reading storage state from ${options.storageState}:
` + e.message);
throw e;
}
}
function prepareRecordHarOptions(options) {
if (!options) return;
if (!options)
return;
return {
path: options.path,
content: options.content || (options.omitContent ? 'omit' : undefined),
urlGlob: (0, _utils.isString)(options.urlFilter) ? options.urlFilter : undefined,
urlRegexSource: (0, _utils.isRegExp)(options.urlFilter) ? options.urlFilter.source : undefined,
urlRegexFlags: (0, _utils.isRegExp)(options.urlFilter) ? options.urlFilter.flags : undefined,
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(options) {
if (options.videoSize && !options.videosPath) throw new Error(`"videoSize" option requires "videosPath" to be specified`);
if (options.extraHTTPHeaders) network.validateHeaders(options.extraHTTPHeaders);
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 ? undefined : options.viewport,
viewport: options.viewport === null ? void 0 : options.viewport,
noDefaultViewport: options.viewport === null,
extraHTTPHeaders: options.extraHTTPHeaders ? (0, _utils.headersObjectToArray)(options.extraHTTPHeaders) : undefined,
storageState: await prepareStorageState(options),
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,
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(options.clientCertificates)
clientCertificates: await toClientCertificatesProtocol(platform, options.clientCertificates)
};
if (!contextParams.recordVideo && options.videosPath) {
contextParams.recordVideo = {
@@ -535,21 +503,27 @@ async function prepareBrowserContextParams(options) {
size: options.videoSize
};
}
if (contextParams.recordVideo && contextParams.recordVideo.dir) contextParams.recordVideo.dir = _path.default.resolve(process.cwd(), contextParams.recordVideo.dir);
if (contextParams.recordVideo && contextParams.recordVideo.dir)
contextParams.recordVideo.dir = platform.path().resolve(contextParams.recordVideo.dir);
return contextParams;
}
function toAcceptDownloadsProtocol(acceptDownloads) {
if (acceptDownloads === undefined) return undefined;
if (acceptDownloads) return 'accept';
return 'deny';
if (acceptDownloads === void 0)
return void 0;
if (acceptDownloads)
return "accept";
return "deny";
}
async function toClientCertificatesProtocol(certs) {
if (!certs) return undefined;
async function toClientCertificatesProtocol(platform, certs) {
if (!certs)
return void 0;
const bufferizeContent = async (value, path) => {
if (value) return value;
if (path) return await _fs.default.promises.readFile(path);
if (value)
return value;
if (path)
return await platform.fs().promises.readFile(path);
};
return await Promise.all(certs.map(async cert => ({
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),
@@ -557,3 +531,9 @@ async function toClientCertificatesProtocol(certs) {
passphrase: cert.passphrase
})));
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BrowserContext,
prepareBrowserContextParams,
toClientCertificatesProtocol
});

View File

@@ -1,222 +1,169 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.BrowserType = void 0;
var _browser3 = require("./browser");
var _browserContext = require("./browserContext");
var _channelOwner = require("./channelOwner");
var _connection = require("./connection");
var _events = require("./events");
var _clientHelper = require("./clientHelper");
var _utils = require("../utils");
var _timeoutRunner = require("../utils/timeoutRunner");
/**
* 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.
*/
// This is here just for api generation and checking.
class BrowserType extends _channelOwner.ChannelOwner {
constructor(...args) {
super(...args);
this._serverLauncher = void 0;
this._contexts = new Set();
this._playwright = void 0;
// Instrumentation.
this._defaultContextOptions = void 0;
this._defaultContextTimeout = void 0;
this._defaultContextNavigationTimeout = void 0;
this._defaultLaunchOptions = void 0;
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');
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 = {}) {
var _this$_defaultLaunchO;
(0, _utils.assert)(!options.userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
(0, _utils.assert)(!options.port, 'Cannot specify a port without launching as a server.');
const logger = options.logger || ((_this$_defaultLaunchO = this._defaultLaunchOptions) === null || _this$_defaultLaunchO === void 0 ? void 0 : _this$_defaultLaunchO.logger);
options = {
...this._defaultLaunchOptions,
...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 : undefined,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined
env: options.env ? (0, import_clientHelper.envObjectToArray)(options.env) : void 0
};
return await this._wrapApiCall(async () => {
const browser = _browser3.Browser.from((await this._channel.launch(launchOptions)).browser);
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._defaultLaunchOptions,
...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 = {}) {
var _this$_defaultLaunchO2;
const logger = options.logger || ((_this$_defaultLaunchO2 = this._defaultLaunchOptions) === null || _this$_defaultLaunchO2 === void 0 ? void 0 : _this$_defaultLaunchO2.logger);
(0, _utils.assert)(!options.port, 'Cannot specify a port without launching as a server.');
options = {
...this._defaultLaunchOptions,
...this._defaultContextOptions,
...options
};
const contextParams = await (0, _browserContext.prepareBrowserContextParams)(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 : undefined,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : void 0,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? (0, _clientHelper.envObjectToArray)(options.env) : undefined,
env: options.env ? (0, import_clientHelper.envObjectToArray)(options.env) : void 0,
channel: options.channel,
userDataDir
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 = _browserContext.BrowserContext.from(result.context);
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, _utils.assert)(optionsOrWsEndpoint.wsEndpoint, 'options.wsEndpoint is required');
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 () => {
var _params$exposeNetwork;
const deadline = params.timeout ? (0, _utils.monotonicTime)() + params.timeout : 0;
const headers = {
'x-playwright-browser': this.name(),
...params.headers
};
const localUtils = this._connection.localUtils();
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) !== null && _params$exposeNetwork !== void 0 ? _params$exposeNetwork : params._exposeNetwork,
exposeNetwork: params.exposeNetwork ?? params._exposeNetwork,
slowMo: params.slowMo,
timeout: params.timeout
};
if (params.__testHookRedirectPortForwarding) connectParams.socksProxyRedirectPortForTest = params.__testHookRedirectPortForwarding;
const {
pipe,
headers: connectHeaders
} = await localUtils._channel.connect(connectParams);
const closePipe = () => pipe.close().catch(() => {});
const connection = new _connection.Connection(localUtils, this._instrumentation);
connection.markAsRemote();
connection.on('close', closePipe);
if (params.__testHookRedirectPortForwarding)
connectParams.socksProxyRedirectPortForTest = params.__testHookRedirectPortForwarding;
const connection = await (0, import_webSocket.connectOverWebSocket)(this._connection, connectParams);
let browser;
let closeError;
const onPipeClosed = reason => {
// Emulate all pages, contexts and the browser closing upon disconnect.
for (const context of ((_browser = browser) === null || _browser === void 0 ? void 0 : _browser.contexts()) || []) {
var _browser;
for (const page of context.pages()) page._onClose();
connection.on("close", () => {
for (const context of browser?.contexts() || []) {
for (const page of context.pages())
page._onClose();
context._onClose();
}
connection.close(reason || closeError);
// Give a chance to any API call promises to reject upon page/context closure.
// This happens naturally when we receive page.onClose and browser.onClose from the server
// in separate tasks. However, upon pipe closure we used to dispatch them all synchronously
// here and promises did not have a chance to reject.
// The order of rejects vs closure is a part of the API contract and our test runner
// relies on it to attribute rejections to the right test.
setTimeout(() => {
var _browser2;
return (_browser2 = browser) === null || _browser2 === void 0 ? void 0 : _browser2._didClose();
}, 0);
};
pipe.on('closed', params => onPipeClosed(params.reason));
connection.onmessage = message => this._wrapApiCall(() => pipe.send({
message
}).catch(() => onPipeClosed()), /* isInternal */true);
pipe.on('message', ({
message
}) => {
try {
connection.dispatch(message);
} catch (e) {
closeError = String(e);
closePipe();
}
setTimeout(() => browser?._didClose(), 0);
});
const result = await (0, _timeoutRunner.raceAgainstDeadline)(async () => {
// For tests.
if (params.__testHookBeforeCreateBrowser) await params.__testHookBeforeCreateBrowser();
const result = await (0, import_timeoutRunner.raceAgainstDeadline)(async () => {
if (params.__testHookBeforeCreateBrowser)
await params.__testHookBeforeCreateBrowser();
const playwright = await connection.initializePlaywright();
if (!playwright._initializer.preLaunchedBrowser) {
closePipe();
throw new Error('Malformed endpoint. Did you use BrowserType.launchServer method?');
connection.close();
throw new Error("Malformed endpoint. Did you use BrowserType.launchServer method?");
}
playwright._setSelectors(this._playwright.selectors);
browser = _browser3.Browser.from(playwright._initializer.preLaunchedBrowser);
browser = import_browser.Browser.from(playwright._initializer.preLaunchedBrowser);
this._didLaunchBrowser(browser, {}, logger);
browser._shouldCloseConnectionOnClose = true;
browser._connectHeaders = connectHeaders;
browser.on(_events.Events.Browser.Disconnected, () => this._wrapApiCall(() => closePipe(), /* isInternal */true));
browser.on(import_events.Events.Browser.Disconnected, () => connection.close());
return browser;
}, deadline);
if (!result.timedOut) {
return result.result;
} else {
closePipe();
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, _utils.assert)(endpointURL, 'Cannot connect over CDP without wsEndpoint.');
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, _utils.headersObjectToArray)(params.headers) : undefined;
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 = _browser3.Browser.from(result.browser);
const browser = import_browser.Browser.from(result.browser);
this._didLaunchBrowser(browser, {}, params.logger);
if (result.defaultContext) await this._didCreateContext(_browserContext.BrowserContext.from(result.defaultContext), {}, {}, params.logger);
if (result.defaultContext)
await this._didCreateContext(import_browserContext.BrowserContext.from(result.defaultContext), {}, {}, params.logger);
return browser;
}
_didLaunchBrowser(browser, browserOptions, logger) {
@@ -229,8 +176,10 @@ class BrowserType extends _channelOwner.ChannelOwner {
context._browserType = this;
this._contexts.add(context);
context._setOptions(contextOptions, browserOptions);
if (this._defaultContextTimeout !== undefined) context.setDefaultTimeout(this._defaultContextTimeout);
if (this._defaultContextNavigationTimeout !== undefined) context.setDefaultNavigationTimeout(this._defaultContextNavigationTimeout);
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) {
@@ -238,4 +187,7 @@ class BrowserType extends _channelOwner.ChannelOwner {
await this._instrumentation.runBeforeCloseBrowserContext(context);
}
}
exports.BrowserType = BrowserType;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BrowserType
});

View File

@@ -1,36 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.CDPSession = void 0;
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class CDPSession extends _channelOwner.ChannelOwner {
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._channel.on("event", ({ method, params }) => {
this.emit(method, params);
});
this.on = super.on;
@@ -40,14 +38,14 @@ class CDPSession extends _channelOwner.ChannelOwner {
this.once = super.once;
}
async send(method, params) {
const result = await this._channel.send({
method,
params
});
const result = await this._channel.send({ method, params });
return result.result;
}
async detach() {
return await this._channel.detach();
}
}
exports.CDPSession = CDPSession;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
CDPSession
});

View File

@@ -1,58 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.ChannelOwner = void 0;
var _eventEmitter = require("./eventEmitter");
var _validator = require("../protocol/validator");
var _debugLogger = require("../utils/debugLogger");
var _stackTrace = require("../utils/stackTrace");
var _utils = require("../utils");
var _zones = require("../utils/zones");
/**
* 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.
*/
class ChannelOwner extends _eventEmitter.EventEmitter {
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) {
super();
this._connection = void 0;
this._parent = void 0;
this._objects = new Map();
this._type = void 0;
this._guid = void 0;
this._channel = void 0;
this._initializer = void 0;
this._logger = void 0;
this._instrumentation = void 0;
this._eventToSubscriptionMapping = new Map();
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 = parent instanceof ChannelOwner ? parent._connection : parent;
this._connection = connection;
this._type = type;
this._guid = guid;
this._parent = parent instanceof ChannelOwner ? parent : undefined;
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 _eventEmitter.EventEmitter());
this._channel = this._createChannel(new import_eventEmitter.EventEmitter(connection._platform));
this._initializer = initializer;
}
markAsInternalType() {
@@ -65,36 +57,39 @@ class ChannelOwner extends _eventEmitter.EventEmitter {
const protocolEvent = this._eventToSubscriptionMapping.get(String(event));
if (protocolEvent) {
this._wrapApiCall(async () => {
await this._channel.updateSubscription({
event: protocolEvent,
enabled
await this._channel.updateSubscription({ event: protocolEvent, enabled });
}, true).catch(() => {
});
}, true).catch(() => {});
}
}
on(event, listener) {
if (!this.listenerCount(event)) this._updateSubscription(event, true);
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);
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);
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);
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);
if (!this.listenerCount(event))
this._updateSubscription(event, false);
return this;
}
_adopt(child) {
@@ -103,44 +98,44 @@ class ChannelOwner extends _eventEmitter.EventEmitter {
child._parent = this;
}
_dispose(reason) {
// Clean up from parent and connection.
if (this._parent) this._parent._objects.delete(this._guid);
if (this._parent)
this._parent._objects.delete(this._guid);
this._connection._objects.delete(this._guid);
this._wasCollected = reason === 'gc';
// Dispose all children.
for (const object of [...this._objects.values()]) object._dispose(reason);
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())
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, _validator.maybeFindValidator)(this._type, prop, 'Params');
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, '', {
tChannelImpl: tChannelImplToWire,
binary: this._connection.rawBuffers() ? 'buffer' : 'toBase64'
});
return async (params) => {
return await this._wrapApiCall(async (apiZone) => {
const validatedParams = validator(params, "", this._validatorToWireContext());
if (!apiZone.isInternal && !apiZone.reported) {
// Reporting/tracing/logging this api call for the first time.
apiZone.params = params;
apiZone.reported = true;
this._instrumentation.onApiCallBegin(apiZone);
logApiCall(this._logger, `=> ${apiZone.apiName} started`);
logApiCall(this._platform, this._logger, `=> ${apiZone.apiName} started`);
return await this._connection.sendMessageToServer(this, prop, validatedParams, apiZone.apiName, apiZone.frames, apiZone.stepId);
}
// Since this api call is either internal, or has already been reported/traced once,
// passing undefined apiName will avoid an extra unneeded tracing entry.
return await this._connection.sendMessageToServer(this, prop, validatedParams, undefined, [], undefined);
return await this._connection.sendMessageToServer(this, prop, validatedParams, void 0, [], void 0);
});
};
}
@@ -153,63 +148,58 @@ class ChannelOwner extends _eventEmitter.EventEmitter {
}
async _wrapApiCall(func, isInternal) {
const logger = this._logger;
const existingApiZone = _zones.zones.zoneData('apiZone');
if (existingApiZone) return await func(existingApiZone);
if (isInternal === undefined) isInternal = this._isInternalType;
const stackTrace = (0, _stackTrace.captureLibraryStackTrace)();
const apiZone = {
apiName: stackTrace.apiName,
frames: stackTrace.frames,
isInternal,
reported: false,
userData: undefined,
stepId: undefined
};
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 _zones.zones.run('apiZone', apiZone, async () => await func(apiZone));
const result = await this._platform.zones.current().push(apiZone).run(async () => await func(apiZone));
if (!isInternal) {
logApiCall(logger, `<= ${apiZone.apiName} succeeded`);
logApiCall(this._platform, logger, `<= ${apiZone.apiName} succeeded`);
this._instrumentation.onApiCallEnd(apiZone);
}
return result;
} catch (e) {
const innerError = (process.env.PWDEBUGIMPL || (0, _utils.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, _stackTrace.stringifyStackFrames)(stackTrace.frames).join('\n') + innerError;
if (stackFrames.trim()) e.stack = e.message + stackFrames;else e.stack = '';
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(logger, `<= ${apiZone.apiName} failed`);
logApiCall(this._platform, logger, `<= ${apiZone.apiName} failed`);
this._instrumentation.onApiCallEnd(apiZone);
}
throw e;
}
}
_toImpl() {
var _this$_connection$toI, _this$_connection;
return (_this$_connection$toI = (_this$_connection = this._connection).toImpl) === null || _this$_connection$toI === void 0 ? void 0 : _this$_connection$toI.call(_this$_connection, this);
return this._connection.toImpl?.(this);
}
toJSON() {
// Jest's expect library tries to print objects sometimes.
// RPC objects can contain links to lots of other objects,
// which can cause jest to crash. Let's help it out
// by just returning the important values.
return {
_type: this._type,
_guid: this._guid
};
}
}
exports.ChannelOwner = ChannelOwner;
function logApiCall(logger, message) {
if (logger && logger.isEnabled('api', 'info')) logger.log('api', 'info', message, [], {
color: 'cyan'
});
_debugLogger.debugLogger.log('api', message);
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 _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
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,57 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.addSourceUrlToScript = addSourceUrlToScript;
exports.envObjectToArray = envObjectToArray;
exports.evaluationScript = evaluationScript;
var _fs = _interopRequireDefault(require("fs"));
var _utils = require("../utils");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
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], undefined)) result.push({
name,
value: String(env[name])
});
if (!Object.is(env[name], void 0))
result.push({ name, value: String(env[name]) });
}
return result;
}
async function evaluationScript(fun, arg, addSourceUrl = true) {
if (typeof fun === 'function') {
async function evaluationScript(platform, fun, arg, addSourceUrl = true) {
if (typeof fun === "function") {
const source = fun.toString();
const argString = Object.is(arg, undefined) ? 'undefined' : JSON.stringify(arg);
const argString = Object.is(arg, void 0) ? "undefined" : JSON.stringify(arg);
return `(${source})(${argString})`;
}
if (arg !== undefined) throw new Error('Cannot evaluate a string with arguments');
if ((0, _utils.isString)(fun)) return fun;
if (fun.content !== undefined) return fun.content;
if (fun.path !== undefined) {
let source = await _fs.default.promises.readFile(fun.path, 'utf8');
if (addSourceUrl) source = addSourceUrlToScript(source, fun.path);
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');
throw new Error("Either path or content property must be present");
}
function addSourceUrlToScript(source, path) {
return `${source}\n//# sourceURL=${path.replace(/\n/g, '')}`;
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,52 +1,55 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.createInstrumentation = createInstrumentation;
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
*
* 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.
*/
// Instrumentation can mutate the data, for example change apiName or stepId.
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')) {
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) {
var _prop, _ref;
await ((_prop = (_ref = listener)[prop]) === null || _prop === void 0 ? void 0 : _prop.call(_ref, ...params));
}
for (const listener of listeners)
await listener[prop]?.(...params);
};
}
if (prop.startsWith('on')) {
if (prop.startsWith("on")) {
return (...params) => {
for (const listener of listeners) {
var _prop2, _ref2;
(_prop2 = (_ref2 = listener)[prop]) === null || _prop2 === void 0 || _prop2.call(_ref2, ...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,32 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Clock = void 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.
*/
module.exports = __toCommonJS(clock_exports);
class Clock {
constructor(browserContext) {
this._browserContext = void 0;
this._browserContext = browserContext;
}
async install(options = {}) {
await this._browserContext._channel.clockInstall(options.time !== undefined ? parseTime(options.time) : {});
await this._browserContext._channel.clockInstall(options.time !== void 0 ? parseTime(options.time) : {});
}
async fastForward(ticks) {
await this._browserContext._channel.clockFastForward(parseTicks(ticks));
@@ -47,22 +47,22 @@ class Clock {
await this._browserContext._channel.clockSetSystemTime(parseTime(time));
}
}
exports.Clock = Clock;
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()
};
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 : undefined,
ticksString: typeof ticks === 'string' ? ticks : undefined
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,84 +1,81 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Connection = void 0;
var _browser = require("./browser");
var _browserContext = require("./browserContext");
var _browserType = require("./browserType");
var _channelOwner = require("./channelOwner");
var _elementHandle = require("./elementHandle");
var _frame = require("./frame");
var _jsHandle = require("./jsHandle");
var _network = require("./network");
var _page = require("./page");
var _worker = require("./worker");
var _dialog = require("./dialog");
var _errors = require("./errors");
var _cdpSession = require("./cdpSession");
var _playwright = require("./playwright");
var _electron = require("./electron");
var _stream = require("./stream");
var _writableStream = require("./writableStream");
var _debugLogger = require("../utils/debugLogger");
var _selectors = require("./selectors");
var _android = require("./android");
var _artifact = require("./artifact");
var _events = require("events");
var _jsonPipe = require("./jsonPipe");
var _fetch = require("./fetch");
var _localUtils = require("./localUtils");
var _tracing = require("./tracing");
var _validator = require("../protocol/validator");
var _clientInstrumentation = require("./clientInstrumentation");
var _utils = require("../utils");
/**
* 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.
*/
class Root extends _channelOwner.ChannelOwner {
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', '', {});
super(connection, "Root", "", {});
}
async initialize() {
return _playwright.Playwright.from((await this._channel.initialize({
sdkLanguage: 'javascript'
return import_playwright.Playwright.from((await this._channel.initialize({
sdkLanguage: "javascript"
})).playwright);
}
}
class DummyChannelOwner extends _channelOwner.ChannelOwner {}
class Connection extends _events.EventEmitter {
constructor(localUtils, instrumentation) {
super();
this._objects = new Map();
this.onmessage = message => {};
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 = new Map();
this._rootObject = void 0;
this._closedError = void 0;
this._callbacks = /* @__PURE__ */ new Map();
this._isRemote = false;
this._localUtils = void 0;
this._rawBuffers = false;
// Some connections allow resolving in-process dispatchers.
this.toImpl = void 0;
this._tracingCount = 0;
this._instrumentation = void 0;
this._instrumentation = instrumentation || (0, _clientInstrumentation.createInstrumentation)();
this._instrumentation = instrumentation || (0, import_clientInstrumentation.createInstrumentation)();
this._localUtils = localUtils;
this._rootObject = new Root(this);
this.headers = headers;
}
markAsRemote() {
this._isRemote = true;
@@ -102,232 +99,220 @@ class Connection extends _events.EventEmitter {
return this._objects.get(guid);
}
setIsTracing(isTracing) {
if (isTracing) this._tracingCount++;else this._tracingCount--;
if (isTracing)
this._tracingCount++;
else
this._tracingCount--;
}
async sendMessageToServer(object, method, params, apiName, frames, stepId) {
var _this$_localUtils;
if (this._closedError) throw this._closedError;
if (object._wasCollected) throw new Error('The object has been collected to prevent unbounded heap growth.');
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 (_debugLogger.debugLogger.isEnabled('channel')) {
// Do not include metadata in debug logs to avoid noise.
_debugLogger.debugLogger.log('channel', 'SEND> ' + JSON.stringify(message));
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
} : undefined;
const metadata = {
apiName,
location,
internal: !apiName,
stepId
};
if (this._tracingCount && frames && type !== 'LocalUtils') (_this$_localUtils = this._localUtils) === null || _this$_localUtils === void 0 || _this$_localUtils._channel.addStackToTracingNoReply({
callData: {
stack: frames,
id
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 }));
}
}).catch(() => {});
// We need to exit zones before calling into the server, otherwise
// when we receive events from the server, we would be in an API zone.
_utils.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 (this._closedError)
return;
const { id, guid, method, params, result, error, log } = message;
if (id) {
if (_debugLogger.debugLogger.isEnabled('channel')) _debugLogger.debugLogger.log('channel', '<RECV ' + JSON.stringify(message));
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}`);
if (!callback)
throw new Error(`Cannot find command to respond: ${id}`);
this._callbacks.delete(id);
if (error && !result) {
const parsedError = (0, _errors.parseError)(error);
(0, _utils.rewriteErrorMessage)(parsedError, parsedError.message + (0, _utils.formatCallLog)(log));
const parsedError = (0, import_errors.parseError)(error);
(0, import_stackTrace.rewriteErrorMessage)(parsedError, parsedError.message + formatCallLog(this._platform, log));
callback.reject(parsedError);
} else {
const validator = (0, _validator.findValidator)(callback.type, callback.method, 'Result');
callback.resolve(validator(result, '', {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this._rawBuffers ? 'buffer' : 'fromBase64'
}));
const validator2 = (0, import_validator.findValidator)(callback.type, callback.method, "Result");
callback.resolve(validator2(result, "", this._validatorFromWireContext()));
}
return;
}
if (_debugLogger.debugLogger.isEnabled('channel')) _debugLogger.debugLogger.log('channel', '<EVENT ' + JSON.stringify(message));
if (method === '__create__') {
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__') {
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}`);
if (!child)
throw new Error(`Unknown new child: ${params.guid}`);
object._adopt(child);
return;
}
if (method === '__dispose__') {
if (method === "__dispose__") {
object._dispose(params.reason);
return;
}
const validator = (0, _validator.findValidator)(object._type, method, 'Event');
object._channel.emit(method, validator(params, '', {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this._rawBuffers ? 'buffer' : 'fromBase64'
}));
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 _errors.TargetClosedError(cause);
for (const callback of this._callbacks.values()) callback.reject(this._closedError);
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');
this.emit("close");
}
_tChannelImplFromWire(names, arg, path, context) {
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
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 _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
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 _validator.ValidationError(`${path}: expected channel ${names.toString()}`);
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}`);
if (!parent)
throw new Error(`Cannot find parent object ${parentGuid} to create ${guid}`);
let result;
const validator = (0, _validator.findValidator)(type, '', 'Initializer');
initializer = validator(initializer, '', {
tChannelImpl: this._tChannelImplFromWire.bind(this),
binary: this._rawBuffers ? 'buffer' : 'fromBase64'
});
const validator = (0, import_validator.findValidator)(type, "", "Initializer");
initializer = validator(initializer, "", this._validatorFromWireContext());
switch (type) {
case 'Android':
result = new _android.Android(parent, type, guid, initializer);
case "Android":
result = new import_android.Android(parent, type, guid, initializer);
break;
case 'AndroidSocket':
result = new _android.AndroidSocket(parent, type, guid, initializer);
case "AndroidSocket":
result = new import_android.AndroidSocket(parent, type, guid, initializer);
break;
case 'AndroidDevice':
result = new _android.AndroidDevice(parent, type, guid, initializer);
case "AndroidDevice":
result = new import_android.AndroidDevice(parent, type, guid, initializer);
break;
case 'APIRequestContext':
result = new _fetch.APIRequestContext(parent, type, guid, initializer);
case "APIRequestContext":
result = new import_fetch.APIRequestContext(parent, type, guid, initializer);
break;
case 'Artifact':
result = new _artifact.Artifact(parent, type, guid, initializer);
case "Artifact":
result = new import_artifact.Artifact(parent, type, guid, initializer);
break;
case 'BindingCall':
result = new _page.BindingCall(parent, type, guid, initializer);
case "BindingCall":
result = new import_page.BindingCall(parent, type, guid, initializer);
break;
case 'Browser':
result = new _browser.Browser(parent, type, guid, initializer);
case "Browser":
result = new import_browser.Browser(parent, type, guid, initializer);
break;
case 'BrowserContext':
result = new _browserContext.BrowserContext(parent, type, guid, initializer);
case "BrowserContext":
result = new import_browserContext.BrowserContext(parent, type, guid, initializer);
break;
case 'BrowserType':
result = new _browserType.BrowserType(parent, type, guid, initializer);
case "BrowserType":
result = new import_browserType.BrowserType(parent, type, guid, initializer);
break;
case 'CDPSession':
result = new _cdpSession.CDPSession(parent, type, guid, initializer);
case "CDPSession":
result = new import_cdpSession.CDPSession(parent, type, guid, initializer);
break;
case 'Dialog':
result = new _dialog.Dialog(parent, type, guid, initializer);
case "Dialog":
result = new import_dialog.Dialog(parent, type, guid, initializer);
break;
case 'Electron':
result = new _electron.Electron(parent, type, guid, initializer);
case "Electron":
result = new import_electron.Electron(parent, type, guid, initializer);
break;
case 'ElectronApplication':
result = new _electron.ElectronApplication(parent, type, guid, initializer);
case "ElectronApplication":
result = new import_electron.ElectronApplication(parent, type, guid, initializer);
break;
case 'ElementHandle':
result = new _elementHandle.ElementHandle(parent, type, guid, initializer);
case "ElementHandle":
result = new import_elementHandle.ElementHandle(parent, type, guid, initializer);
break;
case 'Frame':
result = new _frame.Frame(parent, type, guid, initializer);
case "Frame":
result = new import_frame.Frame(parent, type, guid, initializer);
break;
case 'JSHandle':
result = new _jsHandle.JSHandle(parent, type, guid, initializer);
case "JSHandle":
result = new import_jsHandle.JSHandle(parent, type, guid, initializer);
break;
case 'JsonPipe':
result = new _jsonPipe.JsonPipe(parent, type, guid, initializer);
case "JsonPipe":
result = new import_jsonPipe.JsonPipe(parent, type, guid, initializer);
break;
case 'LocalUtils':
result = new _localUtils.LocalUtils(parent, type, guid, initializer);
if (!this._localUtils) this._localUtils = result;
case "LocalUtils":
result = new import_localUtils.LocalUtils(parent, type, guid, initializer);
if (!this._localUtils)
this._localUtils = result;
break;
case 'Page':
result = new _page.Page(parent, type, guid, initializer);
case "Page":
result = new import_page.Page(parent, type, guid, initializer);
break;
case 'Playwright':
result = new _playwright.Playwright(parent, type, guid, initializer);
case "Playwright":
result = new import_playwright.Playwright(parent, type, guid, initializer);
break;
case 'Request':
result = new _network.Request(parent, type, guid, initializer);
case "Request":
result = new import_network.Request(parent, type, guid, initializer);
break;
case 'Response':
result = new _network.Response(parent, type, guid, initializer);
case "Response":
result = new import_network.Response(parent, type, guid, initializer);
break;
case 'Route':
result = new _network.Route(parent, type, guid, initializer);
case "Route":
result = new import_network.Route(parent, type, guid, initializer);
break;
case 'Stream':
result = new _stream.Stream(parent, type, guid, initializer);
case "Stream":
result = new import_stream.Stream(parent, type, guid, initializer);
break;
case 'Selectors':
result = new _selectors.SelectorsOwner(parent, type, guid, initializer);
case "Selectors":
result = new import_selectors.SelectorsOwner(parent, type, guid, initializer);
break;
case 'SocksSupport':
case "SocksSupport":
result = new DummyChannelOwner(parent, type, guid, initializer);
break;
case 'Tracing':
result = new _tracing.Tracing(parent, type, guid, initializer);
case "Tracing":
result = new import_tracing.Tracing(parent, type, guid, initializer);
break;
case 'WebSocket':
result = new _network.WebSocket(parent, type, guid, initializer);
case "WebSocket":
result = new import_network.WebSocket(parent, type, guid, initializer);
break;
case 'WebSocketRoute':
result = new _network.WebSocketRoute(parent, type, guid, initializer);
case "WebSocketRoute":
result = new import_network.WebSocketRoute(parent, type, guid, initializer);
break;
case 'Worker':
result = new _worker.Worker(parent, type, guid, initializer);
case "Worker":
result = new import_worker.Worker(parent, type, guid, initializer);
break;
case 'WritableStream':
result = new _writableStream.WritableStream(parent, type, guid, initializer);
case "WritableStream":
result = new import_writableStream.WritableStream(parent, type, guid, initializer);
break;
default:
throw new Error('Missing type ' + type);
throw new Error("Missing type " + type);
}
return result;
}
}
exports.Connection = Connection;
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,37 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.ConsoleMessage = void 0;
var util = _interopRequireWildcard(require("util"));
var _jsHandle = require("./jsHandle");
var _page = require("./page");
let _util$inspect$custom;
/**
* 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.
*/
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
_util$inspect$custom = util.inspect.custom;
module.exports = __toCommonJS(consoleMessage_exports);
var import_jsHandle = require("./jsHandle");
var import_page = require("./page");
class ConsoleMessage {
constructor(event) {
this._page = void 0;
this._event = void 0;
this._page = 'page' in event && event.page ? _page.Page.from(event.page) : null;
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;
@@ -43,13 +40,16 @@ class ConsoleMessage {
return this._event.text;
}
args() {
return this._event.args.map(_jsHandle.JSHandle.from);
return this._event.args.map(import_jsHandle.JSHandle.from);
}
location() {
return this._event.location;
}
[_util$inspect$custom]() {
_inspect() {
return this.text();
}
}
exports.ConsoleMessage = ConsoleMessage;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ConsoleMessage
});

View File

@@ -1,28 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Coverage = void 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.
*/
module.exports = __toCommonJS(coverage_exports);
class Coverage {
constructor(channel) {
this._channel = void 0;
this._channel = channel;
}
async startJSCoverage(options = {}) {
@@ -38,4 +38,7 @@ class Coverage {
return (await this._channel.stopCSSCoverage()).entries;
}
}
exports.Coverage = Coverage;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Coverage
});

View File

@@ -1,37 +1,35 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Dialog = void 0;
var _channelOwner = require("./channelOwner");
var _page = require("./page");
/**
* 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.
*/
class Dialog extends _channelOwner.ChannelOwner {
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);
// Note: dialogs that open early during page initialization block it.
// Therefore, we must report the dialog without a page to be able to handle it.
this._page = void 0;
this._page = _page.Page.fromNullable(initializer.page);
this._page = import_page.Page.fromNullable(initializer.page);
}
page() {
return this._page;
@@ -46,12 +44,13 @@ class Dialog extends _channelOwner.ChannelOwner {
return this._initializer.defaultValue;
}
async accept(promptText) {
await this._channel.accept({
promptText
});
await this._channel.accept({ promptText });
}
async dismiss() {
await this._channel.dismiss();
}
}
exports.Dialog = Dialog;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Dialog
});

View File

@@ -1,31 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Download = void 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.
*/
module.exports = __toCommonJS(download_exports);
class Download {
constructor(page, url, suggestedFilename, artifact) {
this._page = void 0;
this._url = void 0;
this._suggestedFilename = void 0;
this._artifact = void 0;
this._page = page;
this._url = url;
this._suggestedFilename = suggestedFilename;
@@ -59,4 +56,7 @@ class Download {
return await this._artifact.delete();
}
}
exports.Download = Download;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Download
});

View File

@@ -1,35 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.ElectronApplication = exports.Electron = void 0;
var _timeoutSettings = require("../common/timeoutSettings");
var _browserContext = require("./browserContext");
var _channelOwner = require("./channelOwner");
var _clientHelper = require("./clientHelper");
var _events = require("./events");
var _jsHandle = require("./jsHandle");
var _consoleMessage = require("./consoleMessage");
var _waiter = require("./waiter");
var _errors = require("./errors");
let _Symbol$asyncDispose;
/**
* 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.
*/
class Electron extends _channelOwner.ChannelOwner {
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;
}
@@ -38,8 +40,8 @@ class Electron extends _channelOwner.ChannelOwner {
}
async launch(options = {}) {
const params = {
...(await (0, _browserContext.prepareBrowserContextParams)(options)),
env: (0, _clientHelper.envObjectToArray)(options.env ? options.env : process.env),
...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);
@@ -47,89 +49,85 @@ class Electron extends _channelOwner.ChannelOwner {
return app;
}
}
exports.Electron = Electron;
_Symbol$asyncDispose = Symbol.asyncDispose;
class ElectronApplication extends _channelOwner.ChannelOwner {
static from(electronApplication) {
return electronApplication._object;
}
class ElectronApplication extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._context = void 0;
this._windows = new Set();
this._timeoutSettings = new _timeoutSettings.TimeoutSettings();
this._context = _browserContext.BrowserContext.from(initializer.context);
for (const page of this._context._pages) this._onPage(page);
this._context.on(_events.Events.BrowserContext.Page, page => this._onPage(page));
this._channel.on('close', () => {
this.emit(_events.Events.ElectronApplication.Close);
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(_events.Events.ElectronApplication.Console, new _consoleMessage.ConsoleMessage(event)));
this._setEventToSubscriptionMapping(new Map([[_events.Events.ElectronApplication.Console, 'console']]));
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(_events.Events.ElectronApplication.Window, page);
page.once(_events.Events.Page.Close, () => this._windows.delete(page));
this.emit(import_events.Events.ElectronApplication.Window, page);
page.once(import_events.Events.Page.Close, () => this._windows.delete(page));
}
windows() {
// TODO: add ElectronPage class inheriting from Page.
return [...this._windows];
}
async firstWindow(options) {
if (this._windows.size) return this._windows.values().next().value;
return await this.waitForEvent('window', options);
if (this._windows.size)
return this._windows.values().next().value;
return await this.waitForEvent("window", options);
}
context() {
return this._context;
}
async [_Symbol$asyncDispose]() {
async [Symbol.asyncDispose]() {
await this.close();
}
async close() {
try {
await this._context.close();
} catch (e) {
if ((0, _errors.isTargetClosedError)(e)) return;
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 = _waiter.Waiter.createForEvent(this, event);
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 !== _events.Events.ElectronApplication.Close) waiter.rejectOnEvent(this, _events.Events.ElectronApplication.Close, () => new _errors.TargetClosedError());
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 _jsHandle.JSHandle.from(result.handle);
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, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
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, _jsHandle.serializeArgument)(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
});
return _jsHandle.JSHandle.from(result.handle);
}
}
exports.ElectronApplication = ElectronApplication;

View File

@@ -1,41 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.ElementHandle = void 0;
exports.convertInputFiles = convertInputFiles;
exports.convertSelectOptionValues = convertSelectOptionValues;
exports.determineScreenshotType = determineScreenshotType;
var _frame = require("./frame");
var _jsHandle = require("./jsHandle");
var _fs = _interopRequireDefault(require("fs"));
var _utilsBundle = require("../utilsBundle");
var _path = _interopRequireDefault(require("path"));
var _utils = require("../utils");
var _fileUtils = require("../utils/fileUtils");
var _writableStream = require("./writableStream");
var _stream = require("stream");
var _util = require("util");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* 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 pipelineAsync = (0, _util.promisify)(_stream.pipeline);
class ElementHandle extends _jsHandle.JSHandle {
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;
}
@@ -44,30 +40,31 @@ class ElementHandle extends _jsHandle.JSHandle {
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._elementChannel = void 0;
this._elementChannel = this._channel;
}
asElement() {
return this;
}
async ownerFrame() {
return _frame.Frame.fromNullable((await this._elementChannel.ownerFrame()).frame);
return import_frame.Frame.fromNullable((await this._elementChannel.ownerFrame()).frame);
}
async contentFrame() {
return _frame.Frame.fromNullable((await this._elementChannel.contentFrame()).frame);
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 === undefined ? null : value;
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 === undefined ? null : value;
return value === void 0 ? null : value;
}
async innerText() {
return (await this._elementChannel.innerText()).value;
@@ -94,10 +91,7 @@ class ElementHandle extends _jsHandle.JSHandle {
return (await this._elementChannel.isVisible()).value;
}
async dispatchEvent(type, eventInit = {}) {
await this._elementChannel.dispatchEvent({
type,
eventInit: (0, _jsHandle.serializeArgument)(eventInit)
});
await this._elementChannel.dispatchEvent({ type, eventInit: (0, import_jsHandle.serializeArgument)(eventInit) });
}
async scrollIntoViewIfNeeded(options = {}) {
await this._elementChannel.scrollIntoViewIfNeeded(options);
@@ -115,44 +109,30 @@ class ElementHandle extends _jsHandle.JSHandle {
return await this._elementChannel.tap(options);
}
async selectOption(values, options = {}) {
const result = await this._elementChannel.selectOption({
...convertSelectOptionValues(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
});
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(files, frame.page().context());
await this._elementChannel.setInputFiles({
...converted,
...options
});
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
});
await this._elementChannel.type({ text, ...options });
}
async press(key, options = {}) {
await this._elementChannel.press({
key,
...options
});
await this._elementChannel.press({ key, ...options });
}
async check(options = {}) {
return await this._elementChannel.check(options);
@@ -161,143 +141,117 @@ class ElementHandle extends _jsHandle.JSHandle {
return await this._elementChannel.uncheck(options);
}
async setChecked(checked, options) {
if (checked) await this.check(options);else await this.uncheck(options);
if (checked)
await this.check(options);
else
await this.uncheck(options);
}
async boundingBox() {
const value = (await this._elementChannel.boundingBox()).value;
return value === undefined ? null : value;
return value === void 0 ? null : value;
}
async screenshot(options = {}) {
const copy = {
...options,
mask: undefined
};
if (!copy.type) copy.type = determineScreenshotType(options);
if (options.mask) {
copy.mask = options.mask.map(locator => ({
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, _fileUtils.mkdirIfNeeded)(options.path);
await _fs.default.promises.writeFile(options.path, result.binary);
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);
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));
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, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
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, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
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
});
return await this._elementChannel.waitForElementState({ state, ...options });
}
async waitForSelector(selector, options = {}) {
const result = await this._elementChannel.waitForSelector({
selector,
...options
});
const result = await this._elementChannel.waitForSelector({ selector, ...options });
return ElementHandle.fromNullable(result.element);
}
}
exports.ElementHandle = ElementHandle;
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, _utils.assert)(values[i] !== null, `options[${i}]: expected object, got null`);
if (values[0] instanceof ElementHandle) return {
elements: values.map(v => v._elementChannel)
};
if ((0, _utils.isString)(values[0])) return {
options: values.map(valueOrLabel => ({
valueOrLabel
}))
};
return {
options: 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) >= _fileUtils.fileUploadSizeLimit;
return payloads.reduce((size, item) => size + (item.buffer ? item.buffer.byteLength : 0), 0) >= import_fileUtils.fileUploadSizeLimit;
}
async function resolvePathsAndDirectoryForInputFiles(items) {
var _localPaths2;
async function resolvePathsAndDirectoryForInputFiles(platform, items) {
let localPaths;
let localDirectory;
for (const item of items) {
const stat = await _fs.default.promises.stat(item);
const stat = await platform.fs().promises.stat(item);
if (stat.isDirectory()) {
if (localDirectory) throw new Error('Multiple directories are not supported');
localDirectory = _path.default.resolve(item);
if (localDirectory)
throw new Error("Multiple directories are not supported");
localDirectory = platform.path().resolve(item);
} else {
var _localPaths;
(_localPaths = localPaths) !== null && _localPaths !== void 0 ? _localPaths : localPaths = [];
localPaths.push(_path.default.resolve(item));
localPaths ??= [];
localPaths.push(platform.path().resolve(item));
}
}
if ((_localPaths2 = localPaths) !== null && _localPaths2 !== void 0 && _localPaths2.length && localDirectory) throw new Error('File paths must be all files or a single directory');
if (localPaths?.length && localDirectory)
throw new Error("File paths must be all files or a single directory");
return [localPaths, localDirectory];
}
async function convertInputFiles(files, context) {
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(items);
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 files = localDirectory ? (await _fs.default.promises.readdir(localDirectory, {
withFileTypes: true,
recursive: true
})).filter(f => f.isFile()).map(f => _path.default.join(f.path, f.name)) : localPaths;
const {
writableStreams,
rootDir
} = await context._wrapApiCall(async () => context._channel.createTempFiles({
rootDirName: localDirectory ? _path.default.basename(localDirectory) : undefined,
items: await Promise.all(files.map(async file => {
const lastModifiedMs = (await _fs.default.promises.stat(file)).mtimeMs;
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 ? _path.default.relative(localDirectory, file) : _path.default.basename(file),
name: localDirectory ? platform.path().relative(localDirectory, file) : platform.path().basename(file),
lastModifiedMs
};
}))
}), true);
for (let i = 0; i < files.length; i++) {
const writable = _writableStream.WritableStream.from(writableStreams[i]);
await pipelineAsync(_fs.default.createReadStream(files[i]), writable.stream());
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 ? undefined : writableStreams
streams: localDirectory ? void 0 : writableStreams
};
}
return {
@@ -306,16 +260,25 @@ async function convertInputFiles(files, context) {
};
}
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
};
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 = _utilsBundle.mime.getType(options.path);
if (mimeType === 'image/png') return 'png';else if (mimeType === 'image/jpeg') return 'jpeg';
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 +1,77 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.TimeoutError = exports.TargetClosedError = void 0;
exports.isTargetClosedError = isTargetClosedError;
exports.parseError = parseError;
exports.serializeError = serializeError;
var _utils = require("../utils");
var _serializers = require("../protocol/serializers");
/**
* 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.
*/
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';
this.name = "TimeoutError";
}
}
exports.TimeoutError = TimeoutError;
class TargetClosedError extends Error {
constructor(cause) {
super(cause || 'Target page, context or browser has been closed');
super(cause || "Target page, context or browser has been closed");
}
}
exports.TargetClosedError = TargetClosedError;
function isTargetClosedError(error) {
return error instanceof TargetClosedError;
}
function serializeError(e) {
if ((0, _utils.isError)(e)) return {
error: {
message: e.message,
stack: e.stack,
name: e.name
}
};
return {
value: (0, _serializers.serializeValue)(e, value => ({
fallThrough: value
}))
};
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 === undefined) throw new Error('Serialized error must have either an error or a value');
return (0, _serializers.parseSerializedValue)(error.value, undefined);
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 e = new TimeoutError(error.error.message);
e.stack = error.error.stack || '';
return e;
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 e = new TargetClosedError(error.error.message);
e.stack = error.error.stack || '';
return e;
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.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,83 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.EventEmitter = void 0;
var _events = require("events");
var _utils = require("../utils");
/**
* Copyright Joyent, Inc. and other Node contributors.
* Modifications copyright (c) Microsoft Corporation.
*
* 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.
*/
module.exports = __toCommonJS(eventEmitter_exports);
class EventEmitter {
constructor() {
this._events = undefined;
constructor(platform) {
this._events = void 0;
this._eventsCount = 0;
this._maxListeners = undefined;
this._pendingHandlers = new Map();
this._rejectionHandler = void 0;
if (this._events === undefined || this._events === Object.getPrototypeOf(this)._events) {
this._events = Object.create(null);
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 || undefined;
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 + '.');
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 === undefined ? _events.EventEmitter.defaultMaxListeners : this._maxListeners;
return this._maxListeners === void 0 ? this._platform.defaultMaxListeners() : this._maxListeners;
}
emit(type, ...args) {
const events = this._events;
if (events === undefined) return false;
const handler = events === null || events === void 0 ? void 0 : events[type];
if (handler === undefined) return false;
if (typeof handler === 'function') {
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);
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;
if (!(promise instanceof Promise))
return;
let set = this._pendingHandlers.get(type);
if (!set) {
set = new 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;
promise.catch((e) => {
if (this._rejectionHandler)
this._rejectionHandler(e);
else
throw e;
}).finally(() => set.delete(promise));
}
addListener(type, listener) {
@@ -90,48 +89,36 @@ class EventEmitter {
checkListener(listener);
let events = this._events;
let existing;
if (events === undefined) {
events = this._events = Object.create(null);
if (events === void 0) {
events = this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) {
this.emit('newListener', type, unwrapListener(listener));
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
if (events.newListener !== void 0) {
this.emit("newListener", type, unwrapListener(listener));
events = this._events;
}
existing = events[type];
}
if (existing === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
if (existing === void 0) {
existing = events[type] = listener;
++this._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
if (typeof existing === "function") {
existing = events[type] = prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
// Check for listener leak
const m = this.getMaxListeners();
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
const w = new Error('Possible EventEmitter memory leak detected. ' + existing.length + ' ' + String(type) + ' listeners ' + 'added. Use emitter.setMaxListeners() to ' + 'increase limit');
w.name = 'MaxListenersExceededWarning';
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 (!(0, _utils.isUnderTest)()) {
// eslint-disable-next-line no-console
if (!this._platform.isUnderTest()) {
console.warn(w);
}
}
@@ -154,18 +141,20 @@ class EventEmitter {
removeListener(type, listener) {
checkListener(listener);
const events = this._events;
if (events === undefined) return this;
if (events === void 0)
return this;
const list = events[type];
if (list === undefined) return this;
if (list === void 0)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0) {
this._events = Object.create(null);
this._events = /* @__PURE__ */ Object.create(null);
} else {
var _listener;
delete events[type];
if (events.removeListener) this.emit('removeListener', type, (_listener = list.listener) !== null && _listener !== void 0 ? _listener : listener);
if (events.removeListener)
this.emit("removeListener", type, list.listener ?? listener);
}
} else if (typeof list !== 'function') {
} else if (typeof list !== "function") {
let position = -1;
let originalListener;
for (let i = list.length - 1; i >= 0; i--) {
@@ -175,10 +164,16 @@ class EventEmitter {
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 !== undefined) this.emit('removeListener', type, originalListener || listener);
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;
}
@@ -187,55 +182,57 @@ class EventEmitter {
}
removeAllListeners(type, options) {
this._removeAllListeners(type);
if (!options) return this;
if (options.behavior === 'wait') {
if (!options)
return this;
if (options.behavior === "wait") {
const errors = [];
this._rejectionHandler = error => errors.push(error);
// eslint-disable-next-line internal-playwright/await-promise-in-class-returns
this._rejectionHandler = (error) => errors.push(error);
return this._waitFor(type).then(() => {
if (errors.length) throw errors[0];
if (errors.length)
throw errors[0];
});
}
if (options.behavior === 'ignoreErrors') this._rejectionHandler = () => {};
// eslint-disable-next-line internal-playwright/await-promise-in-class-returns
if (options.behavior === "ignoreErrors")
this._rejectionHandler = () => {
};
return Promise.resolve();
}
_removeAllListeners(type) {
const events = this._events;
if (!events) return;
// not listening for removeListener, no need to emit
if (!events)
return;
if (!events.removeListener) {
if (type === undefined) {
this._events = Object.create(null);
if (type === void 0) {
this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0) this._events = Object.create(null);else delete events[type];
} else if (events[type] !== void 0) {
if (--this._eventsCount === 0)
this._events = /* @__PURE__ */ Object.create(null);
else
delete events[type];
}
return;
}
// emit removeListener for all listeners on all events
if (type === undefined) {
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;
if (key === "removeListener")
continue;
this._removeAllListeners(key);
}
this._removeAllListeners('removeListener');
this._events = Object.create(null);
this._removeAllListeners("removeListener");
this._events = /* @__PURE__ */ Object.create(null);
this._eventsCount = 0;
return;
}
const listeners = events[type];
if (typeof listeners === 'function') {
if (typeof listeners === "function") {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (let i = listeners.length - 1; i >= 0; i--) this.removeListener(type, listeners[i]);
} else if (listeners !== void 0) {
for (let i = listeners.length - 1; i >= 0; i--)
this.removeListener(type, listeners[i]);
}
}
listeners(type) {
@@ -246,10 +243,12 @@ class EventEmitter {
}
listenerCount(type) {
const events = this._events;
if (events !== undefined) {
if (events !== void 0) {
const listener = events[type];
if (typeof listener === 'function') return 1;
if (listener !== undefined) return listener.length;
if (typeof listener === "function")
return 1;
if (listener !== void 0)
return listener.length;
}
return 0;
}
@@ -259,33 +258,33 @@ class EventEmitter {
async _waitFor(type) {
let promises = [];
if (type) {
promises = [...(this._pendingHandlers.get(type) || [])];
promises = [...this._pendingHandlers.get(type) || []];
} else {
promises = [];
for (const [, pending] of this._pendingHandlers) promises.push(...pending);
for (const [, pending] of this._pendingHandlers)
promises.push(...pending);
}
await Promise.all(promises);
}
_listeners(target, type, unwrap) {
const events = target._events;
if (events === undefined) return [];
if (events === void 0)
return [];
const listener = events[type];
if (listener === undefined) return [];
if (typeof listener === 'function') return unwrap ? [unwrapListener(listener)] : [listener];
if (listener === void 0)
return [];
if (typeof listener === "function")
return unwrap ? [unwrapListener(listener)] : [listener];
return unwrap ? unwrapListeners(listener) : listener.slice();
}
}
exports.EventEmitter = EventEmitter;
function checkListener(listener) {
if (typeof listener !== 'function') throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof 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.wrapperFunction = void 0;
this._listener = void 0;
this._eventEmitter = void 0;
this._eventType = void 0;
this._eventEmitter = eventEmitter;
this._eventType = eventType;
this._listener = listener;
@@ -293,22 +292,23 @@ class OnceWrapper {
this.wrapperFunction.listener = listener;
}
_handle(...args) {
if (this._fired) return;
if (this._fired)
return;
this._fired = true;
this._eventEmitter.removeListener(this._eventType, this.wrapperFunction);
return this._listener.apply(this._eventEmitter, args);
}
}
function unwrapListener(l) {
var _wrappedListener;
return (_wrappedListener = wrappedListener(l)) !== null && _wrappedListener !== void 0 ? _wrappedListener : l;
return wrappedListener(l) ?? l;
}
function unwrapListeners(arr) {
return arr.map(l => {
var _wrappedListener2;
return (_wrappedListener2 = wrappedListener(l)) !== null && _wrappedListener2 !== void 0 ? _wrappedListener2 : l;
});
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,94 +1,98 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Events = void 0;
/**
* Copyright 2019 Google Inc. All rights reserved.
* Modifications 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 Events = exports.Events = {
module.exports = __toCommonJS(events_exports);
const Events = {
AndroidDevice: {
WebView: 'webview',
Close: 'close'
WebView: "webview",
Close: "close"
},
AndroidSocket: {
Data: 'data',
Close: 'close'
Data: "data",
Close: "close"
},
AndroidWebView: {
Close: 'close'
Close: "close"
},
Browser: {
Disconnected: 'disconnected'
Disconnected: "disconnected"
},
BrowserContext: {
Console: 'console',
Close: 'close',
Dialog: 'dialog',
Page: 'page',
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'
WebError: "weberror",
BackgroundPage: "backgroundpage",
ServiceWorker: "serviceworker",
Request: "request",
Response: "response",
RequestFailed: "requestfailed",
RequestFinished: "requestfinished"
},
BrowserServer: {
Close: 'close'
Close: "close"
},
Page: {
Close: 'close',
Crash: 'crash',
Console: 'console',
Dialog: 'dialog',
Download: 'download',
FileChooser: 'filechooser',
DOMContentLoaded: 'domcontentloaded',
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'
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'
Close: "close",
Error: "socketerror",
FrameReceived: "framereceived",
FrameSent: "framesent"
},
Worker: {
Close: 'close'
Close: "close"
},
ElectronApplication: {
Close: 'close',
Console: 'console',
Window: 'window'
Close: "close",
Console: "console",
Window: "window"
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Events
});

View File

@@ -1,168 +1,159 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.APIResponse = exports.APIRequestContext = exports.APIRequest = void 0;
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
var util = _interopRequireWildcard(require("util"));
var _utils = require("../utils");
var _fileUtils = require("../utils/fileUtils");
var _channelOwner = require("./channelOwner");
var _network = require("./network");
var _tracing = require("./tracing");
var _errors = require("./errors");
var _browserContext = require("./browserContext");
let _Symbol$asyncDispose, _Symbol$asyncDispose2, _util$inspect$custom;
/**
* 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.
*/
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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._playwright = void 0;
this._contexts = new Set();
// Instrumentation.
this._defaultContextOptions = void 0;
this._contexts = /* @__PURE__ */ new Set();
this._playwright = playwright;
}
async newContext(options = {}) {
var _this$_defaultContext;
options = {
...this._defaultContextOptions,
...this._playwright._defaultContextOptions,
timeout: this._playwright._defaultContextTimeout,
...options
};
const storageState = typeof options.storageState === 'string' ? JSON.parse(await _fs.default.promises.readFile(options.storageState, 'utf8')) : options.storageState;
// We do not expose tracesDir in the API, so do not allow options to accidentally override it.
const tracesDir = (_this$_defaultContext = this._defaultContextOptions) === null || _this$_defaultContext === void 0 ? void 0 : _this$_defaultContext.tracesDir;
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, _utils.headersObjectToArray)(options.extraHTTPHeaders) : undefined,
extraHTTPHeaders: options.extraHTTPHeaders ? (0, import_headers.headersObjectToArray)(options.extraHTTPHeaders) : void 0,
storageState,
tracesDir,
clientCertificates: await (0, _browserContext.toClientCertificatesProtocol)(options.clientCertificates)
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 = tracesDir;
context._tracing._tracesDir = this._playwright._defaultLaunchOptions?.tracesDir;
await context._instrumentation.runAfterCreateRequestContext(context);
return context;
}
}
exports.APIRequest = APIRequest;
_Symbol$asyncDispose = Symbol.asyncDispose;
class APIRequestContext extends _channelOwner.ChannelOwner {
class APIRequestContext extends import_channelOwner.ChannelOwner {
static from(channel) {
return channel._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._request = void 0;
this._tracing = void 0;
this._closeReason = void 0;
this._tracing = _tracing.Tracing.from(initializer.tracing);
this._tracing = import_tracing.Tracing.from(initializer.tracing);
}
async [_Symbol$asyncDispose]() {
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose(options = {}) {
var _this$_request;
this._closeReason = options.reason;
await this._instrumentation.runBeforeCloseRequestContext(this);
try {
await this._channel.dispose(options);
} catch (e) {
if ((0, _errors.isTargetClosedError)(e)) return;
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
this._tracing._resetStackCounter();
(_this$_request = this._request) === null || _this$_request === void 0 || _this$_request._contexts.delete(this);
this._request?._contexts.delete(this);
}
async delete(url, options) {
return await this.fetch(url, {
...options,
method: 'DELETE'
method: "DELETE"
});
}
async head(url, options) {
return await this.fetch(url, {
...options,
method: 'HEAD'
method: "HEAD"
});
}
async get(url, options) {
return await this.fetch(url, {
...options,
method: 'GET'
method: "GET"
});
}
async patch(url, options) {
return await this.fetch(url, {
...options,
method: 'PATCH'
method: "PATCH"
});
}
async post(url, options) {
return await this.fetch(url, {
...options,
method: 'POST'
method: "POST"
});
}
async put(url, options) {
return await this.fetch(url, {
...options,
method: 'PUT'
method: "PUT"
});
}
async fetch(urlOrRequest, options = {}) {
const url = (0, _utils.isString)(urlOrRequest) ? urlOrRequest : undefined;
const request = (0, _utils.isString)(urlOrRequest) ? undefined : urlOrRequest;
return await this._innerFetch({
url,
request,
...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 () => {
var _options$request, _options$request2, _options$request3;
if (this._closeReason) throw new _errors.TargetClosedError(this._closeReason);
(0, _utils.assert)(options.request || typeof options.url === 'string', 'First argument must be either URL string or Request');
(0, _utils.assert)((options.data === undefined ? 0 : 1) + (options.form === undefined ? 0 : 1) + (options.multipart === undefined ? 0 : 1) <= 1, `Only one of 'data', 'form' or 'multipart' can be specified`);
(0, _utils.assert)(options.maxRedirects === undefined || options.maxRedirects >= 0, `'maxRedirects' must be greater than or equal to '0'`);
(0, _utils.assert)(options.maxRetries === undefined || options.maxRetries >= 0, `'maxRetries' must be greater than or equal to '0'`);
const url = options.url !== undefined ? options.url : options.request.url();
const method = options.method || ((_options$request = options.request) === null || _options$request === void 0 ? void 0 : _options$request.method());
let encodedParams = undefined;
if (typeof options.params === 'string') encodedParams = options.params;else if (options.params instanceof URLSearchParams) encodedParams = options.params.toString();
// Cannot call allHeaders() here as the request may be paused inside route handler.
const headersObj = options.headers || ((_options$request2 = options.request) === null || _options$request2 === void 0 ? void 0 : _options$request2.headers());
const headers = headersObj ? (0, _utils.headersObjectToArray)(headersObj) : undefined;
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 !== undefined) {
if ((0, _utils.isString)(options.data)) {
if (isJsonContentType(headers)) jsonData = isJsonParsable(options.data) ? options.data : JSON.stringify(options.data);else postDataBuffer = Buffer.from(options.data, 'utf8');
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') {
} 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`);
@@ -171,11 +162,9 @@ class APIRequestContext extends _channelOwner.ChannelOwner {
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
});
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);
@@ -185,35 +174,30 @@ class APIRequestContext extends _channelOwner.ChannelOwner {
if (globalThis.FormData && options.multipart instanceof FormData) {
const form = options.multipart;
for (const [name, value] of form.entries()) {
if ((0, _utils.isString)(value)) {
multipartData.push({
name,
value
});
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
});
multipartData.push({ name, file });
}
}
} else {
// Convert file-like values to ServerFilePayload structs.
for (const [name, value] of Object.entries(options.multipart)) multipartData.push(await toFormField(name, value));
for (const [name, value] of Object.entries(options.multipart))
multipartData.push(await toFormField(this._platform, name, value));
}
}
if (postDataBuffer === undefined && jsonData === undefined && formData === undefined && multipartData === undefined) postDataBuffer = ((_options$request3 = options.request) === null || _options$request3 === void 0 ? void 0 : _options$request3.postDataBuffer()) || undefined;
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) : undefined,
params: typeof options.params === "object" ? objectToArray(options.params) : void 0,
encodedParams,
method,
headers,
@@ -232,54 +216,47 @@ class APIRequestContext extends _channelOwner.ChannelOwner {
});
}
async storageState(options = {}) {
const state = await this._channel.storageState();
const state = await this._channel.storageState({ indexedDB: options.indexedDB });
if (options.path) {
await (0, _fileUtils.mkdirIfNeeded)(options.path);
await _fs.default.promises.writeFile(options.path, JSON.stringify(state, undefined, 2), 'utf8');
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;
}
}
exports.APIRequestContext = APIRequestContext;
async function toFormField(name, value) {
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 (value instanceof _fs.default.ReadStream) {
return {
name,
file: await readStreamToJson(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,
value: String(value)
};
return { name, file: await readStreamToJson(platform, value) };
}
}
function isJsonParsable(value) {
if (typeof value !== 'string') return false;
if (typeof value !== "string")
return false;
try {
JSON.parse(value);
return true;
} catch (e) {
if (e instanceof SyntaxError) return false;else throw e;
if (e instanceof SyntaxError)
return false;
else
throw e;
}
}
_Symbol$asyncDispose2 = Symbol.asyncDispose;
_util$inspect$custom = util.inspect.custom;
class APIResponse {
constructor(context, initializer) {
this._initializer = void 0;
this._headers = void 0;
this._request = void 0;
this._request = context;
this._initializer = initializer;
this._headers = new _network.RawHeaders(this._initializer.headers);
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;
@@ -300,53 +277,46 @@ class APIResponse {
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 === undefined) throw new Error('Response has been disposed');
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, _errors.isTargetClosedError)(e)) throw new Error('Response has been disposed');
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');
return content.toString("utf8");
}
async json() {
const content = await this.text();
return JSON.parse(content);
}
async [_Symbol$asyncDispose2]() {
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose() {
await this._request._channel.disposeAPIResponse({
fetchUid: this._fetchUid()
});
await this._request._channel.disposeAPIResponse({ fetchUid: this._fetchUid() });
}
[_util$inspect$custom]() {
const headers = this.headersArray().map(({
name,
value
}) => ` ${name}: ${value}`);
return `APIResponse: ${this.status()} ${this.statusText()}\n${headers.join('\n')}`;
_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()
});
const { log } = await this._request._channel.fetchLog({ fetchUid: this._fetchUid() });
return log;
}
}
exports.APIResponse = APIResponse;
function filePayloadToJson(payload) {
return {
name: payload.name,
@@ -354,38 +324,44 @@ function filePayloadToJson(payload) {
buffer: payload.buffer
};
}
async function readStreamToJson(stream) {
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));
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;
const streamPath = Buffer.isBuffer(stream.path) ? stream.path.toString("utf8") : stream.path;
return {
name: _path.default.basename(streamPath),
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';
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 undefined;
if (!map)
return void 0;
const result = [];
for (const [name, value] of Object.entries(map)) result.push({
name,
value: String(value)
});
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'];
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,30 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.FileChooser = void 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.
*/
module.exports = __toCommonJS(fileChooser_exports);
class FileChooser {
constructor(page, elementHandle, isMultiple) {
this._page = void 0;
this._elementHandle = void 0;
this._isMultiple = void 0;
this._page = page;
this._elementHandle = elementHandle;
this._isMultiple = isMultiple;
@@ -42,4 +40,7 @@ class FileChooser {
return await this._elementHandle.setInputFiles(files, options);
}
}
exports.FileChooser = FileChooser;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FileChooser
});

View File

@@ -1,124 +1,129 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Frame = void 0;
exports.verifyLoadState = verifyLoadState;
var _utils = require("../utils");
var _channelOwner = require("./channelOwner");
var _locator = require("./locator");
var _locatorUtils = require("../utils/isomorphic/locatorUtils");
var _elementHandle = require("./elementHandle");
var _jsHandle = require("./jsHandle");
var _fs = _interopRequireDefault(require("fs"));
var network = _interopRequireWildcard(require("./network"));
var _events = require("events");
var _waiter = require("./waiter");
var _events2 = require("./events");
var _types = require("./types");
var _clientHelper = require("./clientHelper");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
class Frame extends _channelOwner.ChannelOwner {
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;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._eventEmitter = void 0;
this._loadStates = void 0;
this._parentFrame = null;
this._url = '';
this._name = '';
this._detached = false;
this._childFrames = new Set();
this._page = void 0;
this._eventEmitter = new _events.EventEmitter();
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(_events2.Events.Page.Load, this._page);
if (!this._parentFrame && event.add === 'domcontentloaded' && this._page) this._page.emit(_events2.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(_events2.Events.Page.FrameNavigated, this);
});
}
page() {
return this._page;
}
async goto(url, options = {}) {
const waitUntil = verifyLoadState('waitUntil', options.waitUntil === undefined ? 'load' : options.waitUntil);
return network.Response.fromNullable((await this._channel.goto({
url,
...options,
waitUntil
})).response);
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 _waiter.Waiter(this._page, '');
if (this._page.isClosed()) waiter.rejectImmediately(this._page._closeErrorWithReason());
waiter.rejectOnEvent(this._page, _events2.Events.Page.Close, () => this._page._closeErrorWithReason());
waiter.rejectOnEvent(this._page, _events2.Events.Page.Crash, new Error('Navigation failed because page crashed!'));
waiter.rejectOnEvent(this._page, _events2.Events.Page.FrameDetached, new Error('Navigating frame was detached!'), frame => frame === this);
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 === undefined ? 'load' : options.waitUntil);
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}"` : '';
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 => {
var _this$_page;
// Any failed navigation results in a rejection.
if (event.error) return true;
const navigatedEvent = await waiter.waitForEvent(this._eventEmitter, "navigated", (event) => {
if (event.error)
return true;
waiter.log(` navigated to "${event.url}"`);
return (0, _utils.urlMatches)((_this$_page = this._page) === null || _this$_page === void 0 ? void 0 : _this$_page.context()._options.baseURL, event.url, options.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 = '';
e.stack = "";
await waiter.waitForPromise(Promise.reject(e));
}
if (!this._loadStates.has(waitUntil)) {
await waiter.waitForEvent(this._eventEmitter, 'loadstate', s => {
await waiter.waitForEvent(this._eventEmitter, "loadstate", (s) => {
waiter.log(` "${s}" event fired`);
return s === waitUntil;
});
@@ -129,14 +134,14 @@ class Frame extends _channelOwner.ChannelOwner {
return response;
});
}
async waitForLoadState(state = 'load', options = {}) {
state = verifyLoadState('state', state);
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 => {
await waiter.waitForEvent(this._eventEmitter, "loadstate", (s) => {
waiter.log(` "${s}" event fired`);
return s === state;
});
@@ -145,111 +150,69 @@ class Frame extends _channelOwner.ChannelOwner {
});
}
async waitForURL(url, options = {}) {
var _this$_page2;
if ((0, _utils.urlMatches)((_this$_page2 = this._page) === null || _this$_page2 === void 0 ? void 0 : _this$_page2.context()._options.baseURL, this.url(), url)) return await this.waitForLoadState(options.waitUntil, options);
await this.waitForNavigation({
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 _elementHandle.ElementHandle.from((await this._channel.frameElement()).element);
return import_elementHandle.ElementHandle.from((await this._channel.frameElement()).element);
}
async evaluateHandle(pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpressionHandle({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return _jsHandle.JSHandle.from(result.handle);
(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, _jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
(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, _jsHandle.assertMaxArguments)(arguments.length, 2);
const result = await this._channel.evaluateExpression({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
(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 _elementHandle.ElementHandle.fromNullable(result.element);
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 _elementHandle.ElementHandle.fromNullable(result.element);
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, _jsHandle.serializeArgument)(eventInit),
...options
});
await this._channel.dispatchEvent({ selector, type, eventInit: (0, import_jsHandle.serializeArgument)(eventInit), ...options });
}
async $eval(selector, pageFunction, arg) {
(0, _jsHandle.assertMaxArguments)(arguments.length, 3);
const result = await this._channel.evalOnSelector({
selector,
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
(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, _jsHandle.assertMaxArguments)(arguments.length, 3);
const result = await this._channel.evalOnSelectorAll({
selector,
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
});
return (0, _jsHandle.parseResult)(result.value);
(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 => _elementHandle.ElementHandle.from(e));
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;
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 === undefined ? 'load' : options.waitUntil);
await this._channel.setContent({
html,
...options,
waitUntil
});
const waitUntil = verifyLoadState("waitUntil", options.waitUntil === void 0 ? "load" : options.waitUntil);
await this._channel.setContent({ html, ...options, waitUntil });
}
name() {
return this._name || '';
return this._name || "";
}
url() {
return this._url;
@@ -264,241 +227,160 @@ class Frame extends _channelOwner.ChannelOwner {
return this._detached;
}
async addScriptTag(options = {}) {
const copy = {
...options
};
const copy = { ...options };
if (copy.path) {
copy.content = (await _fs.default.promises.readFile(copy.path)).toString();
copy.content = (0, _clientHelper.addSourceUrlToScript)(copy.content, copy.path);
copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
copy.content = (0, import_clientHelper.addSourceUrlToScript)(copy.content, copy.path);
}
return _elementHandle.ElementHandle.from((await this._channel.addScriptTag({
...copy
})).element);
return import_elementHandle.ElementHandle.from((await this._channel.addScriptTag({ ...copy })).element);
}
async addStyleTag(options = {}) {
const copy = {
...options
};
const copy = { ...options };
if (copy.path) {
copy.content = (await _fs.default.promises.readFile(copy.path)).toString();
copy.content += '/*# sourceURL=' + copy.path.replace(/\n/g, '') + '*/';
copy.content = (await this._platform.fs().promises.readFile(copy.path)).toString();
copy.content += "/*# sourceURL=" + copy.path.replace(/\n/g, "") + "*/";
}
return _elementHandle.ElementHandle.from((await this._channel.addStyleTag({
...copy
})).element);
return import_elementHandle.ElementHandle.from((await this._channel.addStyleTag({ ...copy })).element);
}
async click(selector, options = {}) {
return await this._channel.click({
selector,
...options
});
return await this._channel.click({ selector, ...options });
}
async dblclick(selector, options = {}) {
return await this._channel.dblclick({
selector,
...options
});
return await this._channel.dblclick({ selector, ...options });
}
async dragAndDrop(source, target, options = {}) {
return await this._channel.dragAndDrop({
source,
target,
...options
});
return await this._channel.dragAndDrop({ source, target, ...options });
}
async tap(selector, options = {}) {
return await this._channel.tap({
selector,
...options
});
return await this._channel.tap({ selector, ...options });
}
async fill(selector, value, options = {}) {
return await this._channel.fill({
selector,
value,
...options
});
return await this._channel.fill({ selector, value, ...options });
}
async _highlight(selector) {
return await this._channel.highlight({
selector
});
return await this._channel.highlight({ selector });
}
locator(selector, options) {
return new _locator.Locator(this, selector, options);
return new import_locator.Locator(this, selector, options);
}
getByTestId(testId) {
return this.locator((0, _locatorUtils.getByTestIdSelector)((0, _locator.testIdAttributeName)(), testId));
return this.locator((0, import_locatorUtils.getByTestIdSelector)((0, import_locator.testIdAttributeName)(), testId));
}
getByAltText(text, options) {
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
}
frameLocator(selector) {
return new _locator.FrameLocator(this, selector);
return new import_locator.FrameLocator(this, selector);
}
async focus(selector, options = {}) {
await this._channel.focus({
selector,
...options
});
await this._channel.focus({ selector, ...options });
}
async textContent(selector, options = {}) {
const value = (await this._channel.textContent({
selector,
...options
})).value;
return value === undefined ? null : value;
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;
return (await this._channel.innerText({ selector, ...options })).value;
}
async innerHTML(selector, options = {}) {
return (await this._channel.innerHTML({
selector,
...options
})).value;
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 === undefined ? null : value;
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;
return (await this._channel.inputValue({ selector, ...options })).value;
}
async isChecked(selector, options = {}) {
return (await this._channel.isChecked({
selector,
...options
})).value;
return (await this._channel.isChecked({ selector, ...options })).value;
}
async isDisabled(selector, options = {}) {
return (await this._channel.isDisabled({
selector,
...options
})).value;
return (await this._channel.isDisabled({ selector, ...options })).value;
}
async isEditable(selector, options = {}) {
return (await this._channel.isEditable({
selector,
...options
})).value;
return (await this._channel.isEditable({ selector, ...options })).value;
}
async isEnabled(selector, options = {}) {
return (await this._channel.isEnabled({
selector,
...options
})).value;
return (await this._channel.isEnabled({ selector, ...options })).value;
}
async isHidden(selector, options = {}) {
return (await this._channel.isHidden({
selector,
...options
})).value;
return (await this._channel.isHidden({ selector, ...options })).value;
}
async isVisible(selector, options = {}) {
return (await this._channel.isVisible({
selector,
...options
})).value;
return (await this._channel.isVisible({ selector, ...options })).value;
}
async hover(selector, options = {}) {
await this._channel.hover({
selector,
...options
});
await this._channel.hover({ selector, ...options });
}
async selectOption(selector, values, options = {}) {
return (await this._channel.selectOption({
selector,
...(0, _elementHandle.convertSelectOptionValues)(values),
...options
})).values;
return (await this._channel.selectOption({ selector, ...(0, import_elementHandle.convertSelectOptionValues)(values), ...options })).values;
}
async setInputFiles(selector, files, options = {}) {
const converted = await (0, _elementHandle.convertInputFiles)(files, this.page().context());
await this._channel.setInputFiles({
selector,
...converted,
...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
});
await this._channel.type({ selector, text, ...options });
}
async press(selector, key, options = {}) {
await this._channel.press({
selector,
key,
...options
});
await this._channel.press({ selector, key, ...options });
}
async check(selector, options = {}) {
await this._channel.check({
selector,
...options
});
await this._channel.check({ selector, ...options });
}
async uncheck(selector, options = {}) {
await this._channel.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);
if (checked)
await this.check(selector, options);
else
await this.uncheck(selector, options);
}
async waitForTimeout(timeout) {
await this._channel.waitForTimeout({
timeout
});
await this._channel.waitForTimeout({ timeout });
}
async waitForFunction(pageFunction, arg, options = {}) {
if (typeof options.polling === 'string') (0, _utils.assert)(options.polling === 'raf', 'Unknown polling option: ' + options.polling);
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' ? undefined : options.polling,
pollingInterval: options.polling === "raf" ? void 0 : options.polling,
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: (0, _jsHandle.serializeArgument)(arg)
isFunction: typeof pageFunction === "function",
arg: (0, import_jsHandle.serializeArgument)(arg)
});
return _jsHandle.JSHandle.from(result.handle);
return import_jsHandle.JSHandle.from(result.handle);
}
async title() {
return (await this._channel.title()).value;
}
}
exports.Frame = Frame;
function verifyLoadState(name, waitUntil) {
if (waitUntil === 'networkidle0') waitUntil = 'networkidle';
if (!_types.kLifecycleEvents.has(waitUntil)) throw new Error(`${name}: expected one of (load|domcontentloaded|networkidle|commit)`);
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,43 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.HarRouter = void 0;
var _debugLogger = require("../utils/debugLogger");
let _Symbol$asyncDispose;
/**
* 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.
*/
_Symbol$asyncDispose = Symbol.asyncDispose;
module.exports = __toCommonJS(harRouter_exports);
class HarRouter {
static async create(localUtils, file, notFoundAction, options) {
const {
harId,
error
} = await localUtils._channel.harOpen({
file
});
if (error) throw new Error(error);
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 = void 0;
this._harId = void 0;
this._notFoundAction = void 0;
this._options = void 0;
this._localUtils = localUtils;
this._harId = harId;
this._options = options;
@@ -45,55 +36,52 @@ class HarRouter {
}
async _handle(route) {
const request = route.request();
const response = await this._localUtils._channel.harLookup({
const response = await this._localUtils.harLookup({
harId: this._harId,
url: request.url(),
method: request.method(),
headers: await request.headersArray(),
postData: request.postDataBuffer() || undefined,
postData: request.postDataBuffer() || void 0,
isNavigationRequest: request.isNavigationRequest()
});
if (response.action === 'redirect') {
_debugLogger.debugLogger.log('api', `HAR: ${route.request().url()} redirected to ${response.redirectURL}`);
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 the response status is -1, the request was canceled or stalled, so we just stall it here.
// See https://github.com/microsoft/playwright/issues/29311.
// TODO: it'd be better to abort such requests, but then we likely need to respect the timing,
// because the request might have been stalled for a long time until the very end of the
// test when HAR was recorded but we'd abort it immediately.
if (response.status === -1) 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])),
headers: Object.fromEntries(response.headers.map((h) => [h.name, h.value])),
body: response.body
});
return;
}
if (response.action === 'error') _debugLogger.debugLogger.log('api', 'HAR: ' + response.message);
// Report the error, but fall through to the default handler.
if (this._notFoundAction === 'abort') {
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));
await context.route(this._options.urlMatch || "**/*", (route) => this._handle(route));
}
async addPageRoute(page) {
await page.route(this._options.urlMatch || '**/*', route => this._handle(route));
await page.route(this._options.urlMatch || "**/*", (route) => this._handle(route));
}
async [_Symbol$asyncDispose]() {
async [Symbol.asyncDispose]() {
await this.dispose();
}
dispose() {
this._localUtils._channel.harClose({
harId: this._harId
}).catch(() => {});
this._localUtils.harClose({ harId: this._harId }).catch(() => {
});
}
}
exports.HarRouter = HarRouter;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
HarRouter
});

View File

@@ -1,111 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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
});
exports.Touchscreen = exports.Mouse = exports.Keyboard = void 0;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
module.exports = __toCommonJS(input_exports);
class Keyboard {
constructor(page) {
this._page = void 0;
this._page = page;
}
async down(key) {
await this._page._channel.keyboardDown({
key
});
await this._page._channel.keyboardDown({ key });
}
async up(key) {
await this._page._channel.keyboardUp({
key
});
await this._page._channel.keyboardUp({ key });
}
async insertText(text) {
await this._page._channel.keyboardInsertText({
text
});
await this._page._channel.keyboardInsertText({ text });
}
async type(text, options = {}) {
await this._page._channel.keyboardType({
text,
...options
});
await this._page._channel.keyboardType({ text, ...options });
}
async press(key, options = {}) {
await this._page._channel.keyboardPress({
key,
...options
});
await this._page._channel.keyboardPress({ key, ...options });
}
}
exports.Keyboard = Keyboard;
class Mouse {
constructor(page) {
this._page = void 0;
this._page = page;
}
async move(x, y, options = {}) {
await this._page._channel.mouseMove({
x,
y,
...options
});
await this._page._channel.mouseMove({ x, y, ...options });
}
async down(options = {}) {
await this._page._channel.mouseDown({
...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
});
await this._page._channel.mouseClick({ x, y, ...options });
}
async dblclick(x, y, options = {}) {
await this.click(x, y, {
...options,
clickCount: 2
});
await this.click(x, y, { ...options, clickCount: 2 });
}
async wheel(deltaX, deltaY) {
await this._page._channel.mouseWheel({
deltaX,
deltaY
});
await this._page._channel.mouseWheel({ deltaX, deltaY });
}
}
exports.Mouse = Mouse;
class Touchscreen {
constructor(page) {
this._page = void 0;
this._page = page;
}
async tap(x, y) {
await this._page._channel.touchscreenTap({
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
});
}
}
exports.Touchscreen = Touchscreen;

View File

@@ -1,72 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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 jsHandle_exports = {};
__export(jsHandle_exports, {
JSHandle: () => JSHandle,
assertMaxArguments: () => assertMaxArguments,
parseResult: () => parseResult,
serializeArgument: () => serializeArgument
});
exports.JSHandle = void 0;
exports.assertMaxArguments = assertMaxArguments;
exports.parseResult = parseResult;
exports.serializeArgument = serializeArgument;
var _channelOwner = require("./channelOwner");
var _serializers = require("../protocol/serializers");
var _errors = require("./errors");
let _Symbol$asyncDispose;
/**
* 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.
*/
_Symbol$asyncDispose = Symbol.asyncDispose;
class JSHandle extends _channelOwner.ChannelOwner {
module.exports = __toCommonJS(jsHandle_exports);
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_serializers = require("../protocol/serializers");
class JSHandle extends import_channelOwner.ChannelOwner {
static from(handle) {
return handle._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._preview = void 0;
this._preview = this._initializer.preview;
this._channel.on('previewUpdated', ({
preview
}) => this._preview = preview);
this._channel.on("previewUpdated", ({ preview }) => this._preview = preview);
}
async evaluate(pageFunction, arg) {
const result = await this._channel.evaluateExpression({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: serializeArgument(arg)
});
const result = await this._channel.evaluateExpression({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: serializeArgument(arg) });
return parseResult(result.value);
}
async evaluateHandle(pageFunction, arg) {
const result = await this._channel.evaluateExpressionHandle({
expression: String(pageFunction),
isFunction: typeof pageFunction === 'function',
arg: serializeArgument(arg)
});
const result = await this._channel.evaluateExpressionHandle({ expression: String(pageFunction), isFunction: typeof pageFunction === "function", arg: serializeArgument(arg) });
return JSHandle.from(result.handle);
}
async getProperty(propertyName) {
const result = await this._channel.getProperty({
name: propertyName
});
const result = await this._channel.getProperty({ name: propertyName });
return JSHandle.from(result.handle);
}
async getProperties() {
const map = new Map();
for (const {
name,
value
} of (await this._channel.getPropertyList()).properties) map.set(name, JSHandle.from(value));
const map = /* @__PURE__ */ new Map();
for (const { name, value } of (await this._channel.getPropertyList()).properties)
map.set(name, JSHandle.from(value));
return map;
}
async jsonValue() {
@@ -75,14 +60,15 @@ class JSHandle extends _channelOwner.ChannelOwner {
asElement() {
return null;
}
async [_Symbol$asyncDispose]() {
async [Symbol.asyncDispose]() {
await this.dispose();
}
async dispose() {
try {
await this._channel.dispose();
} catch (e) {
if ((0, _errors.isTargetClosedError)(e)) return;
if ((0, import_errors.isTargetClosedError)(e))
return;
throw e;
}
}
@@ -90,32 +76,30 @@ class JSHandle extends _channelOwner.ChannelOwner {
return this._preview;
}
}
// This function takes care of converting all JSHandles to their channels,
// so that generic channel serializer converts them to guids.
exports.JSHandle = JSHandle;
function serializeArgument(arg) {
const handles = [];
const pushHandle = channel => {
const pushHandle = (channel) => {
handles.push(channel);
return handles.length - 1;
};
const value = (0, _serializers.serializeValue)(arg, value => {
if (value instanceof JSHandle) return {
h: pushHandle(value._channel)
};
return {
fallThrough: value
};
const value = (0, import_serializers.serializeValue)(arg, (value2) => {
if (value2 instanceof JSHandle)
return { h: pushHandle(value2._channel) };
return { fallThrough: value2 };
});
return {
value,
handles
};
return { value, handles };
}
function parseResult(value) {
return (0, _serializers.parseSerializedValue)(value, undefined);
return (0, import_serializers.parseSerializedValue)(value, void 0);
}
function assertMaxArguments(count, max) {
if (count > max) throw new Error('Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.');
if (count > max)
throw new Error("Too many arguments. If you need to pass more than 1 argument to the function wrap them in an object.");
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
JSHandle,
assertMaxArguments,
parseResult,
serializeArgument
});

View File

@@ -1,27 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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 jsonPipe_exports = {};
__export(jsonPipe_exports, {
JsonPipe: () => JsonPipe
});
exports.JsonPipe = void 0;
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class JsonPipe extends _channelOwner.ChannelOwner {
module.exports = __toCommonJS(jsonPipe_exports);
var import_channelOwner = require("./channelOwner");
class JsonPipe extends import_channelOwner.ChannelOwner {
static from(jsonPipe) {
return jsonPipe._object;
}
@@ -32,4 +33,7 @@ class JsonPipe extends _channelOwner.ChannelOwner {
return this._channel;
}
}
exports.JsonPipe = JsonPipe;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
JsonPipe
});

View File

@@ -1,36 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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 localUtils_exports = {};
__export(localUtils_exports, {
LocalUtils: () => LocalUtils
});
exports.LocalUtils = void 0;
var _channelOwner = require("./channelOwner");
/**
* 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.
*/
class LocalUtils extends _channelOwner.ChannelOwner {
module.exports = __toCommonJS(localUtils_exports);
var import_channelOwner = require("./channelOwner");
class LocalUtils extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this.devices = void 0;
this.markAsInternalType();
this.devices = {};
for (const {
name,
descriptor
} of initializer.deviceDescriptors) this.devices[name] = descriptor;
for (const { name, descriptor } of initializer.deviceDescriptors)
this.devices[name] = descriptor;
}
async zip(params) {
return await this._channel.zip(params);
}
async harOpen(params) {
return await this._channel.harOpen(params);
}
async harLookup(params) {
return await this._channel.harLookup(params);
}
async harClose(params) {
return await this._channel.harClose(params);
}
async harUnzip(params) {
return await this._channel.harUnzip(params);
}
async tracingStarted(params) {
return await this._channel.tracingStarted(params);
}
async traceDiscarded(params) {
return await this._channel.traceDiscarded(params);
}
async addStackToTracingNoReply(params) {
return await this._channel.addStackToTracingNoReply(params);
}
}
exports.LocalUtils = LocalUtils;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
LocalUtils
});

View File

@@ -1,71 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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 locator_exports = {};
__export(locator_exports, {
FrameLocator: () => FrameLocator,
Locator: () => Locator,
setTestIdAttribute: () => setTestIdAttribute,
testIdAttributeName: () => testIdAttributeName
});
exports.Locator = exports.FrameLocator = void 0;
exports.setTestIdAttribute = setTestIdAttribute;
exports.testIdAttributeName = testIdAttributeName;
var util = _interopRequireWildcard(require("util"));
var _utils = require("../utils");
var _elementHandle = require("./elementHandle");
var _jsHandle = require("./jsHandle");
var _stringUtils = require("../utils/isomorphic/stringUtils");
var _locatorUtils = require("../utils/isomorphic/locatorUtils");
let _util$inspect$custom;
/**
* 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.
*/
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
_util$inspect$custom = util.inspect.custom;
module.exports = __toCommonJS(locator_exports);
var import_elementHandle = require("./elementHandle");
var import_jsHandle = require("./jsHandle");
var import_locatorGenerators = require("../utils/isomorphic/locatorGenerators");
var import_locatorUtils = require("../utils/isomorphic/locatorUtils");
var import_stringUtils = require("../utils/isomorphic/stringUtils");
var import_rtti = require("../utils/isomorphic/rtti");
var import_time = require("../utils/isomorphic/time");
class Locator {
constructor(frame, selector, options) {
this._frame = void 0;
this._selector = void 0;
this._frame = frame;
this._selector = selector;
if (options !== null && options !== void 0 && options.hasText) this._selector += ` >> internal:has-text=${(0, _stringUtils.escapeForTextSelector)(options.hasText, false)}`;
if (options !== null && options !== void 0 && options.hasNotText) this._selector += ` >> internal:has-not-text=${(0, _stringUtils.escapeForTextSelector)(options.hasNotText, false)}`;
if (options !== null && options !== void 0 && options.has) {
if (options?.hasText)
this._selector += ` >> internal:has-text=${(0, import_stringUtils.escapeForTextSelector)(options.hasText, false)}`;
if (options?.hasNotText)
this._selector += ` >> internal:has-not-text=${(0, import_stringUtils.escapeForTextSelector)(options.hasNotText, false)}`;
if (options?.has) {
const locator = options.has;
if (locator._frame !== frame) throw new Error(`Inner "has" locator must belong to the same frame.`);
if (locator._frame !== frame)
throw new Error(`Inner "has" locator must belong to the same frame.`);
this._selector += ` >> internal:has=` + JSON.stringify(locator._selector);
}
if (options !== null && options !== void 0 && options.hasNot) {
if (options?.hasNot) {
const locator = options.hasNot;
if (locator._frame !== frame) throw new Error(`Inner "hasNot" locator must belong to the same frame.`);
if (locator._frame !== frame)
throw new Error(`Inner "hasNot" locator must belong to the same frame.`);
this._selector += ` >> internal:has-not=` + JSON.stringify(locator._selector);
}
if (options?.visible !== void 0)
this._selector += ` >> visible=${options.visible ? "true" : "false"}`;
if (this._frame._platform.inspectCustom)
this[this._frame._platform.inspectCustom] = () => this._inspect();
}
async _withElement(task, timeout) {
timeout = this._frame.page()._timeoutSettings.timeout({
timeout
});
const deadline = timeout ? (0, _utils.monotonicTime)() + timeout : 0;
timeout = this._frame.page()._timeoutSettings.timeout({ timeout });
const deadline = timeout ? (0, import_time.monotonicTime)() + timeout : 0;
return await this._frame._wrapApiCall(async () => {
const result = await this._frame._channel.waitForSelector({
selector: this._selector,
strict: true,
state: 'attached',
timeout
});
const handle = _elementHandle.ElementHandle.fromNullable(result.element);
if (!handle) throw new Error(`Could not resolve ${this._selector} to DOM Element`);
const result = await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, state: "attached", timeout });
const handle = import_elementHandle.ElementHandle.fromNullable(result.element);
if (!handle)
throw new Error(`Could not resolve ${this._selector} to DOM Element`);
try {
return await task(handle, deadline ? deadline - (0, _utils.monotonicTime)() : 0);
return await task(handle, deadline ? deadline - (0, import_time.monotonicTime)() : 0);
} finally {
await handle.dispose();
}
@@ -78,31 +78,19 @@ class Locator {
return this._frame.page();
}
async boundingBox(options) {
return await this._withElement(h => h.boundingBox(), options === null || options === void 0 ? void 0 : options.timeout);
return await this._withElement((h) => h.boundingBox(), options?.timeout);
}
async check(options = {}) {
return await this._frame.check(this._selector, {
strict: true,
...options
});
return await this._frame.check(this._selector, { strict: true, ...options });
}
async click(options = {}) {
return await this._frame.click(this._selector, {
strict: true,
...options
});
return await this._frame.click(this._selector, { strict: true, ...options });
}
async dblclick(options = {}) {
return await this._frame.dblclick(this._selector, {
strict: true,
...options
});
return await this._frame.dblclick(this._selector, { strict: true, ...options });
}
async dispatchEvent(type, eventInit = {}, options) {
return await this._frame.dispatchEvent(this._selector, type, eventInit, {
strict: true,
...options
});
return await this._frame.dispatchEvent(this._selector, type, eventInit, { strict: true, ...options });
}
async dragTo(target, options = {}) {
return await this._frame.dragAndDrop(this._selector, target._selector, {
@@ -111,68 +99,62 @@ class Locator {
});
}
async evaluate(pageFunction, arg, options) {
return await this._withElement(h => h.evaluate(pageFunction, arg), options === null || options === void 0 ? void 0 : options.timeout);
return await this._withElement((h) => h.evaluate(pageFunction, arg), options?.timeout);
}
async evaluateAll(pageFunction, arg) {
return await this._frame.$$eval(this._selector, pageFunction, arg);
}
async evaluateHandle(pageFunction, arg, options) {
return await this._withElement(h => h.evaluateHandle(pageFunction, arg), options === null || options === void 0 ? void 0 : options.timeout);
return await this._withElement((h) => h.evaluateHandle(pageFunction, arg), options?.timeout);
}
async fill(value, options = {}) {
return await this._frame.fill(this._selector, value, {
strict: true,
...options
});
return await this._frame.fill(this._selector, value, { strict: true, ...options });
}
async clear(options = {}) {
return await this.fill('', options);
return await this.fill("", options);
}
async _highlight() {
// VS Code extension uses this one, keep it for now.
return await this._frame._highlight(this._selector);
}
async highlight() {
return await this._frame._highlight(this._selector);
}
locator(selectorOrLocator, options) {
if ((0, _utils.isString)(selectorOrLocator)) return new Locator(this._frame, this._selector + ' >> ' + selectorOrLocator, options);
if (selectorOrLocator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ' >> internal:chain=' + JSON.stringify(selectorOrLocator._selector), options);
if ((0, import_rtti.isString)(selectorOrLocator))
return new Locator(this._frame, this._selector + " >> " + selectorOrLocator, options);
if (selectorOrLocator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + " >> internal:chain=" + JSON.stringify(selectorOrLocator._selector), options);
}
getByTestId(testId) {
return this.locator((0, _locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
return this.locator((0, import_locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
}
getByAltText(text, options) {
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
}
frameLocator(selector) {
return new FrameLocator(this._frame, this._selector + ' >> ' + selector);
return new FrameLocator(this._frame, this._selector + " >> " + selector);
}
filter(options) {
return new Locator(this._frame, this._selector, options);
}
async elementHandle(options) {
return await this._frame.waitForSelector(this._selector, {
strict: true,
state: 'attached',
...options
});
return await this._frame.waitForSelector(this._selector, { strict: true, state: "attached", ...options });
}
async elementHandles() {
return await this._frame.$$(this._selector);
@@ -181,7 +163,7 @@ class Locator {
return new FrameLocator(this._frame, this._selector);
}
first() {
return new Locator(this._frame, this._selector + ' >> nth=0');
return new Locator(this._frame, this._selector + " >> nth=0");
}
last() {
return new Locator(this._frame, this._selector + ` >> nth=-1`);
@@ -190,246 +172,172 @@ class Locator {
return new Locator(this._frame, this._selector + ` >> nth=${index}`);
}
and(locator) {
if (locator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
if (locator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ` >> internal:and=` + JSON.stringify(locator._selector));
}
or(locator) {
if (locator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
if (locator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._selector + ` >> internal:or=` + JSON.stringify(locator._selector));
}
async focus(options) {
return await this._frame.focus(this._selector, {
strict: true,
...options
});
return await this._frame.focus(this._selector, { strict: true, ...options });
}
async blur(options) {
await this._frame._channel.blur({
selector: this._selector,
strict: true,
...options
});
await this._frame._channel.blur({ selector: this._selector, strict: true, ...options });
}
async count() {
return await this._frame._queryCount(this._selector);
}
async _generateLocatorString() {
return await this._withElement((h) => h._generateLocatorString());
}
async getAttribute(name, options) {
return await this._frame.getAttribute(this._selector, name, {
strict: true,
...options
});
return await this._frame.getAttribute(this._selector, name, { strict: true, ...options });
}
async hover(options = {}) {
return await this._frame.hover(this._selector, {
strict: true,
...options
});
return await this._frame.hover(this._selector, { strict: true, ...options });
}
async innerHTML(options) {
return await this._frame.innerHTML(this._selector, {
strict: true,
...options
});
return await this._frame.innerHTML(this._selector, { strict: true, ...options });
}
async innerText(options) {
return await this._frame.innerText(this._selector, {
strict: true,
...options
});
return await this._frame.innerText(this._selector, { strict: true, ...options });
}
async inputValue(options) {
return await this._frame.inputValue(this._selector, {
strict: true,
...options
});
return await this._frame.inputValue(this._selector, { strict: true, ...options });
}
async isChecked(options) {
return await this._frame.isChecked(this._selector, {
strict: true,
...options
});
return await this._frame.isChecked(this._selector, { strict: true, ...options });
}
async isDisabled(options) {
return await this._frame.isDisabled(this._selector, {
strict: true,
...options
});
return await this._frame.isDisabled(this._selector, { strict: true, ...options });
}
async isEditable(options) {
return await this._frame.isEditable(this._selector, {
strict: true,
...options
});
return await this._frame.isEditable(this._selector, { strict: true, ...options });
}
async isEnabled(options) {
return await this._frame.isEnabled(this._selector, {
strict: true,
...options
});
return await this._frame.isEnabled(this._selector, { strict: true, ...options });
}
async isHidden(options) {
return await this._frame.isHidden(this._selector, {
strict: true,
...options
});
return await this._frame.isHidden(this._selector, { strict: true, ...options });
}
async isVisible(options) {
return await this._frame.isVisible(this._selector, {
strict: true,
...options
});
return await this._frame.isVisible(this._selector, { strict: true, ...options });
}
async press(key, options = {}) {
return await this._frame.press(this._selector, key, {
strict: true,
...options
});
return await this._frame.press(this._selector, key, { strict: true, ...options });
}
async screenshot(options = {}) {
return await this._withElement((h, timeout) => h.screenshot({
...options,
timeout
}), options.timeout);
const mask = options.mask;
return await this._withElement((h, timeout) => h.screenshot({ ...options, mask, timeout }), options.timeout);
}
async ariaSnapshot(options) {
const result = await this._frame._channel.ariaSnapshot({
...options,
selector: this._selector
});
const result = await this._frame._channel.ariaSnapshot({ ...options, mode: options?._mode, selector: this._selector });
return result.snapshot;
}
async scrollIntoViewIfNeeded(options = {}) {
return await this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({
...options,
timeout
}), options.timeout);
return await this._withElement((h, timeout) => h.scrollIntoViewIfNeeded({ ...options, timeout }), options.timeout);
}
async selectOption(values, options = {}) {
return await this._frame.selectOption(this._selector, values, {
strict: true,
...options
});
return await this._frame.selectOption(this._selector, values, { strict: true, ...options });
}
async selectText(options = {}) {
return await this._withElement((h, timeout) => h.selectText({
...options,
timeout
}), options.timeout);
return await this._withElement((h, timeout) => h.selectText({ ...options, timeout }), options.timeout);
}
async setChecked(checked, options) {
if (checked) await this.check(options);else await this.uncheck(options);
if (checked)
await this.check(options);
else
await this.uncheck(options);
}
async setInputFiles(files, options = {}) {
return await this._frame.setInputFiles(this._selector, files, {
strict: true,
...options
});
return await this._frame.setInputFiles(this._selector, files, { strict: true, ...options });
}
async tap(options = {}) {
return await this._frame.tap(this._selector, {
strict: true,
...options
});
return await this._frame.tap(this._selector, { strict: true, ...options });
}
async textContent(options) {
return await this._frame.textContent(this._selector, {
strict: true,
...options
});
return await this._frame.textContent(this._selector, { strict: true, ...options });
}
async type(text, options = {}) {
return await this._frame.type(this._selector, text, {
strict: true,
...options
});
return await this._frame.type(this._selector, text, { strict: true, ...options });
}
async pressSequentially(text, options = {}) {
return await this.type(text, options);
}
async uncheck(options = {}) {
return await this._frame.uncheck(this._selector, {
strict: true,
...options
});
return await this._frame.uncheck(this._selector, { strict: true, ...options });
}
async all() {
return new Array(await this.count()).fill(0).map((e, i) => this.nth(i));
}
async allInnerTexts() {
return await this._frame.$$eval(this._selector, ee => ee.map(e => e.innerText));
return await this._frame.$$eval(this._selector, (ee) => ee.map((e) => e.innerText));
}
async allTextContents() {
return await this._frame.$$eval(this._selector, ee => ee.map(e => e.textContent || ''));
return await this._frame.$$eval(this._selector, (ee) => ee.map((e) => e.textContent || ""));
}
async waitFor(options) {
await this._frame._channel.waitForSelector({
selector: this._selector,
strict: true,
omitReturnValue: true,
...options
});
await this._frame._channel.waitForSelector({ selector: this._selector, strict: true, omitReturnValue: true, ...options });
}
async _expect(expression, options) {
const params = {
selector: this._selector,
expression,
...options,
isNot: !!options.isNot
};
params.expectedValue = (0, _jsHandle.serializeArgument)(options.expectedValue);
const params = { selector: this._selector, expression, ...options, isNot: !!options.isNot };
params.expectedValue = (0, import_jsHandle.serializeArgument)(options.expectedValue);
const result = await this._frame._channel.expect(params);
if (result.received !== undefined) result.received = (0, _jsHandle.parseResult)(result.received);
if (result.received !== void 0)
result.received = (0, import_jsHandle.parseResult)(result.received);
return result;
}
[_util$inspect$custom]() {
_inspect() {
return this.toString();
}
toString() {
return (0, _utils.asLocator)('javascript', this._selector);
return (0, import_locatorGenerators.asLocator)("javascript", this._selector);
}
}
exports.Locator = Locator;
class FrameLocator {
constructor(frame, selector) {
this._frame = void 0;
this._frameSelector = void 0;
this._frame = frame;
this._frameSelector = selector;
}
locator(selectorOrLocator, options) {
if ((0, _utils.isString)(selectorOrLocator)) return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selectorOrLocator, options);
if (selectorOrLocator._frame !== this._frame) throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selectorOrLocator._selector, options);
if ((0, import_rtti.isString)(selectorOrLocator))
return new Locator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selectorOrLocator, options);
if (selectorOrLocator._frame !== this._frame)
throw new Error(`Locators must belong to the same frame.`);
return new Locator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selectorOrLocator._selector, options);
}
getByTestId(testId) {
return this.locator((0, _locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
return this.locator((0, import_locatorUtils.getByTestIdSelector)(testIdAttributeName(), testId));
}
getByAltText(text, options) {
return this.locator((0, _locatorUtils.getByAltTextSelector)(text, options));
return this.locator((0, import_locatorUtils.getByAltTextSelector)(text, options));
}
getByLabel(text, options) {
return this.locator((0, _locatorUtils.getByLabelSelector)(text, options));
return this.locator((0, import_locatorUtils.getByLabelSelector)(text, options));
}
getByPlaceholder(text, options) {
return this.locator((0, _locatorUtils.getByPlaceholderSelector)(text, options));
return this.locator((0, import_locatorUtils.getByPlaceholderSelector)(text, options));
}
getByText(text, options) {
return this.locator((0, _locatorUtils.getByTextSelector)(text, options));
return this.locator((0, import_locatorUtils.getByTextSelector)(text, options));
}
getByTitle(text, options) {
return this.locator((0, _locatorUtils.getByTitleSelector)(text, options));
return this.locator((0, import_locatorUtils.getByTitleSelector)(text, options));
}
getByRole(role, options = {}) {
return this.locator((0, _locatorUtils.getByRoleSelector)(role, options));
return this.locator((0, import_locatorUtils.getByRoleSelector)(role, options));
}
owner() {
return new Locator(this._frame, this._frameSelector);
}
frameLocator(selector) {
return new FrameLocator(this._frame, this._frameSelector + ' >> internal:control=enter-frame >> ' + selector);
return new FrameLocator(this._frame, this._frameSelector + " >> internal:control=enter-frame >> " + selector);
}
first() {
return new FrameLocator(this._frame, this._frameSelector + ' >> nth=0');
return new FrameLocator(this._frame, this._frameSelector + " >> nth=0");
}
last() {
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=-1`);
@@ -438,11 +346,17 @@ class FrameLocator {
return new FrameLocator(this._frame, this._frameSelector + ` >> nth=${index}`);
}
}
exports.FrameLocator = FrameLocator;
let _testIdAttributeName = 'data-testid';
let _testIdAttributeName = "data-testid";
function testIdAttributeName() {
return _testIdAttributeName;
}
function setTestIdAttribute(attributeName) {
_testIdAttributeName = attributeName;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FrameLocator,
Locator,
setTestIdAttribute,
testIdAttributeName
});

View File

@@ -1,58 +1,60 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
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 network_exports = {};
__export(network_exports, {
RawHeaders: () => RawHeaders,
Request: () => Request,
Response: () => Response,
Route: () => Route,
RouteHandler: () => RouteHandler,
WebSocket: () => WebSocket,
WebSocketRoute: () => WebSocketRoute,
WebSocketRouteHandler: () => WebSocketRouteHandler,
validateHeaders: () => validateHeaders
});
exports.WebSocketRouteHandler = exports.WebSocketRoute = exports.WebSocket = exports.RouteHandler = exports.Route = exports.Response = exports.Request = exports.RawHeaders = void 0;
exports.validateHeaders = validateHeaders;
var _url = require("url");
var _channelOwner = require("./channelOwner");
var _frame = require("./frame");
var _worker = require("./worker");
var _fs = _interopRequireDefault(require("fs"));
var _utilsBundle = require("../utilsBundle");
var _utils = require("../utils");
var _manualPromise = require("../utils/manualPromise");
var _events = require("./events");
var _waiter = require("./waiter");
var _fetch = require("./fetch");
var _errors = require("./errors");
let _Symbol$asyncDispose;
/**
* 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.
*/
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
class Request extends _channelOwner.ChannelOwner {
static from(request) {
return request._object;
}
static fromNullable(request) {
return request ? Request.from(request) : null;
}
module.exports = __toCommonJS(network_exports);
var import_channelOwner = require("./channelOwner");
var import_errors = require("./errors");
var import_events = require("./events");
var import_fetch = require("./fetch");
var import_frame = require("./frame");
var import_waiter = require("./waiter");
var import_worker = require("./worker");
var import_assert = require("../utils/isomorphic/assert");
var import_headers = require("../utils/isomorphic/headers");
var import_urlMatch = require("../utils/isomorphic/urlMatch");
var import_manualPromise = require("../utils/isomorphic/manualPromise");
var import_multimap = require("../utils/isomorphic/multimap");
var import_rtti = require("../utils/isomorphic/rtti");
var import_stackTrace = require("../utils/isomorphic/stackTrace");
var import_mimeType = require("../utils/isomorphic/mimeType");
class Request extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._redirectedFrom = null;
this._redirectedTo = null;
this._failureText = null;
this._provisionalHeaders = void 0;
this._actualHeadersPromise = void 0;
this._timing = void 0;
this._fallbackOverrides = {};
this.markAsInternalType();
this._redirectedFrom = Request.fromNullable(initializer.redirectedFrom);
if (this._redirectedFrom) this._redirectedFrom._redirectedTo = this;
if (this._redirectedFrom)
this._redirectedFrom._redirectedTo = this;
this._provisionalHeaders = new RawHeaders(initializer.headers);
this._timing = {
startTime: 0,
@@ -66,6 +68,12 @@ class Request extends _channelOwner.ChannelOwner {
responseEnd: -1
};
}
static from(request) {
return request._object;
}
static fromNullable(request) {
return request ? Request.from(request) : null;
}
url() {
return this._fallbackOverrides.url || this._initializer.url;
}
@@ -76,38 +84,40 @@ class Request extends _channelOwner.ChannelOwner {
return this._fallbackOverrides.method || this._initializer.method;
}
postData() {
var _ref;
return ((_ref = this._fallbackOverrides.postDataBuffer || this._initializer.postData) === null || _ref === void 0 ? void 0 : _ref.toString('utf-8')) || null;
return (this._fallbackOverrides.postDataBuffer || this._initializer.postData)?.toString("utf-8") || null;
}
postDataBuffer() {
return this._fallbackOverrides.postDataBuffer || this._initializer.postData || null;
}
postDataJSON() {
const postData = this.postData();
if (!postData) return null;
const contentType = this.headers()['content-type'];
if (contentType !== null && contentType !== void 0 && contentType.includes('application/x-www-form-urlencoded')) {
if (!postData)
return null;
const contentType = this.headers()["content-type"];
if (contentType?.includes("application/x-www-form-urlencoded")) {
const entries = {};
const parsed = new _url.URLSearchParams(postData);
for (const [k, v] of parsed.entries()) entries[k] = v;
const parsed = new URLSearchParams(postData);
for (const [k, v] of parsed.entries())
entries[k] = v;
return entries;
}
try {
return JSON.parse(postData);
} catch (e) {
throw new Error('POST data is not a valid JSON object: ' + postData);
throw new Error("POST data is not a valid JSON object: " + postData);
}
}
/**
* @deprecated
*/
headers() {
if (this._fallbackOverrides.headers) return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers).headers();
if (this._fallbackOverrides.headers)
return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers).headers();
return this._provisionalHeaders.headers();
}
async _actualHeaders() {
if (this._fallbackOverrides.headers) return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers);
if (this._fallbackOverrides.headers)
return RawHeaders._fromHeadersObjectLossy(this._fallbackOverrides.headers);
if (!this._actualHeadersPromise) {
this._actualHeadersPromise = this._wrapApiCall(async () => {
return new RawHeaders((await this._channel.rawRequestHeaders()).headers);
@@ -134,21 +144,24 @@ class Request extends _channelOwner.ChannelOwner {
}
frame() {
if (!this._initializer.frame) {
(0, _utils.assert)(this.serviceWorker());
throw new Error('Service Worker requests do not have an associated frame.');
(0, import_assert.assert)(this.serviceWorker());
throw new Error("Service Worker requests do not have an associated frame.");
}
const frame = _frame.Frame.from(this._initializer.frame);
const frame = import_frame.Frame.from(this._initializer.frame);
if (!frame._page) {
throw new Error(['Frame for this navigation request is not available, because the request', 'was issued before the frame is created. You can check whether the request', 'is a navigation request by calling isNavigationRequest() method.'].join('\n'));
throw new Error([
"Frame for this navigation request is not available, because the request",
"was issued before the frame is created. You can check whether the request",
"is a navigation request by calling isNavigationRequest() method."
].join("\n"));
}
return frame;
}
_safePage() {
var _Frame$fromNullable;
return ((_Frame$fromNullable = _frame.Frame.fromNullable(this._initializer.frame)) === null || _Frame$fromNullable === void 0 ? void 0 : _Frame$fromNullable._page) || null;
return import_frame.Frame.fromNullable(this._initializer.frame)?._page || null;
}
serviceWorker() {
return this._initializer.serviceWorker ? _worker.Worker.from(this._initializer.serviceWorker) : null;
return this._initializer.serviceWorker ? import_worker.Worker.from(this._initializer.serviceWorker) : null;
}
isNavigationRequest() {
return this._initializer.isNavigationRequest;
@@ -160,7 +173,8 @@ class Request extends _channelOwner.ChannelOwner {
return this._redirectedTo;
}
failure() {
if (this._failureText === null) return null;
if (this._failureText === null)
return null;
return {
errorText: this._failureText
};
@@ -170,53 +184,57 @@ class Request extends _channelOwner.ChannelOwner {
}
async sizes() {
const response = await this.response();
if (!response) throw new Error('Unable to fetch sizes for failed request');
if (!response)
throw new Error("Unable to fetch sizes for failed request");
return (await response._channel.sizes()).sizes;
}
_setResponseEndTiming(responseEndTiming) {
this._timing.responseEnd = responseEndTiming;
if (this._timing.responseStart === -1) this._timing.responseStart = responseEndTiming;
if (this._timing.responseStart === -1)
this._timing.responseStart = responseEndTiming;
}
_finalRequest() {
return this._redirectedTo ? this._redirectedTo._finalRequest() : this;
}
_applyFallbackOverrides(overrides) {
if (overrides.url) this._fallbackOverrides.url = overrides.url;
if (overrides.method) this._fallbackOverrides.method = overrides.method;
if (overrides.headers) this._fallbackOverrides.headers = overrides.headers;
if ((0, _utils.isString)(overrides.postData)) this._fallbackOverrides.postDataBuffer = Buffer.from(overrides.postData, 'utf-8');else if (overrides.postData instanceof Buffer) this._fallbackOverrides.postDataBuffer = overrides.postData;else if (overrides.postData) this._fallbackOverrides.postDataBuffer = Buffer.from(JSON.stringify(overrides.postData), 'utf-8');
if (overrides.url)
this._fallbackOverrides.url = overrides.url;
if (overrides.method)
this._fallbackOverrides.method = overrides.method;
if (overrides.headers)
this._fallbackOverrides.headers = overrides.headers;
if ((0, import_rtti.isString)(overrides.postData))
this._fallbackOverrides.postDataBuffer = Buffer.from(overrides.postData, "utf-8");
else if (overrides.postData instanceof Buffer)
this._fallbackOverrides.postDataBuffer = overrides.postData;
else if (overrides.postData)
this._fallbackOverrides.postDataBuffer = Buffer.from(JSON.stringify(overrides.postData), "utf-8");
}
_fallbackOverridesForContinue() {
return this._fallbackOverrides;
}
_targetClosedScope() {
var _this$serviceWorker, _this$_safePage;
return ((_this$serviceWorker = this.serviceWorker()) === null || _this$serviceWorker === void 0 ? void 0 : _this$serviceWorker._closedScope) || ((_this$_safePage = this._safePage()) === null || _this$_safePage === void 0 ? void 0 : _this$_safePage._closedOrCrashedScope) || new _manualPromise.LongStandingScope();
return this.serviceWorker()?._closedScope || this._safePage()?._closedOrCrashedScope || new import_manualPromise.LongStandingScope();
}
}
exports.Request = Request;
class Route extends _channelOwner.ChannelOwner {
static from(route) {
return route._object;
}
class Route extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._handlingPromise = null;
this._context = void 0;
this._didThrow = false;
this.markAsInternalType();
}
static from(route) {
return route._object;
}
request() {
return Request.from(this._initializer.request);
}
async _raceWithTargetClose(promise) {
// When page closes or crashes, we catch any potential rejects from this Route.
// Note that page could be missing when routing popup's initial request that
// does not have a Page initialized just yet.
return await this.request()._targetClosedScope().safeRace(promise);
}
async _startHandling() {
this._handlingPromise = new _manualPromise.ManualPromise();
this._handlingPromise = new import_manualPromise.ManualPromise();
return await this._handlingPromise;
}
async fallback(options = {}) {
@@ -226,25 +244,17 @@ class Route extends _channelOwner.ChannelOwner {
}
async abort(errorCode) {
await this._handleRoute(async () => {
await this._raceWithTargetClose(this._channel.abort({
errorCode
}));
await this._raceWithTargetClose(this._channel.abort({ errorCode }));
});
}
async _redirectNavigationRequest(url) {
await this._handleRoute(async () => {
await this._raceWithTargetClose(this._channel.redirectNavigationRequest({
url
}));
await this._raceWithTargetClose(this._channel.redirectNavigationRequest({ url }));
});
}
async fetch(options = {}) {
return await this._wrapApiCall(async () => {
return await this._context.request._innerFetch({
request: this.request(),
data: options.postData,
...options
});
return await this._context.request._innerFetch({ request: this.request(), data: options.postData, ...options });
});
}
async fulfill(options = {}) {
@@ -266,45 +276,50 @@ class Route extends _channelOwner.ChannelOwner {
}
async _innerFulfill(options = {}) {
let fetchResponseUid;
let {
status: statusOption,
headers: headersOption,
body
} = options;
if (options.json !== undefined) {
(0, _utils.assert)(options.body === undefined, 'Can specify either body or json parameters');
let { status: statusOption, headers: headersOption, body } = options;
if (options.json !== void 0) {
(0, import_assert.assert)(options.body === void 0, "Can specify either body or json parameters");
body = JSON.stringify(options.json);
}
if (options.response instanceof _fetch.APIResponse) {
var _statusOption, _headersOption;
(_statusOption = statusOption) !== null && _statusOption !== void 0 ? _statusOption : statusOption = options.response.status();
(_headersOption = headersOption) !== null && _headersOption !== void 0 ? _headersOption : headersOption = options.response.headers();
if (body === undefined && options.path === undefined) {
if (options.response._request._connection === this._connection) fetchResponseUid = options.response._fetchUid();else body = await options.response.body();
if (options.response instanceof import_fetch.APIResponse) {
statusOption ??= options.response.status();
headersOption ??= options.response.headers();
if (body === void 0 && options.path === void 0) {
if (options.response._request._connection === this._connection)
fetchResponseUid = options.response._fetchUid();
else
body = await options.response.body();
}
}
let isBase64 = false;
let length = 0;
if (options.path) {
const buffer = await _fs.default.promises.readFile(options.path);
body = buffer.toString('base64');
const buffer = await this._platform.fs().promises.readFile(options.path);
body = buffer.toString("base64");
isBase64 = true;
length = buffer.length;
} else if ((0, _utils.isString)(body)) {
} else if ((0, import_rtti.isString)(body)) {
isBase64 = false;
length = Buffer.byteLength(body);
} else if (body) {
length = body.length;
body = body.toString('base64');
body = body.toString("base64");
isBase64 = true;
}
const headers = {};
for (const header of Object.keys(headersOption || {})) headers[header.toLowerCase()] = String(headersOption[header]);
if (options.contentType) headers['content-type'] = String(options.contentType);else if (options.json) headers['content-type'] = 'application/json';else if (options.path) headers['content-type'] = _utilsBundle.mime.getType(options.path) || 'application/octet-stream';
if (length && !('content-length' in headers)) headers['content-length'] = String(length);
for (const header of Object.keys(headersOption || {}))
headers[header.toLowerCase()] = String(headersOption[header]);
if (options.contentType)
headers["content-type"] = String(options.contentType);
else if (options.json)
headers["content-type"] = "application/json";
else if (options.path)
headers["content-type"] = (0, import_mimeType.getMimeTypeForPath)(options.path) || "application/octet-stream";
if (length && !("content-length" in headers))
headers["content-length"] = String(length);
await this._raceWithTargetClose(this._channel.fulfill({
status: statusOption || 200,
headers: (0, _utils.headersObjectToArray)(headers),
headers: (0, import_headers.headersObjectToArray)(headers),
body,
isBase64,
fetchResponseUid
@@ -313,11 +328,15 @@ class Route extends _channelOwner.ChannelOwner {
async continue(options = {}) {
await this._handleRoute(async () => {
this.request()._applyFallbackOverrides(options);
await this._innerContinue(false /* isFallback */);
await this._innerContinue(
false
/* isFallback */
);
});
}
_checkNotHandled() {
if (!this._handlingPromise) throw new Error('Route is already handled!');
if (!this._handlingPromise)
throw new Error("Route is already handled!");
}
_reportHandled(done) {
const chain = this._handlingPromise;
@@ -329,32 +348,22 @@ class Route extends _channelOwner.ChannelOwner {
return await this._raceWithTargetClose(this._channel.continue({
url: options.url,
method: options.method,
headers: options.headers ? (0, _utils.headersObjectToArray)(options.headers) : undefined,
headers: options.headers ? (0, import_headers.headersObjectToArray)(options.headers) : void 0,
postData: options.postDataBuffer,
isFallback
}));
}
}
exports.Route = Route;
_Symbol$asyncDispose = Symbol.asyncDispose;
class WebSocketRoute extends _channelOwner.ChannelOwner {
static from(route) {
return route._object;
}
class WebSocketRoute extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._onPageMessage = void 0;
this._onPageClose = void 0;
this._onServerMessage = void 0;
this._onServerClose = void 0;
this._server = void 0;
this._connected = false;
this.markAsInternalType();
this._server = {
onMessage: handler => {
onMessage: (handler) => {
this._onServerMessage = handler;
},
onClose: handler => {
onClose: (handler) => {
this._onServerClose = handler;
},
connectToServer: () => {
@@ -364,88 +373,75 @@ class WebSocketRoute extends _channelOwner.ChannelOwner {
return this._initializer.url;
},
close: async (options = {}) => {
await this._channel.closeServer({
...options,
wasClean: true
}).catch(() => {});
await this._channel.closeServer({ ...options, wasClean: true }).catch(() => {
});
},
send: message => {
if ((0, _utils.isString)(message)) this._channel.sendToServer({
message,
isBase64: false
}).catch(() => {});else this._channel.sendToServer({
message: message.toString('base64'),
isBase64: true
}).catch(() => {});
send: (message) => {
if ((0, import_rtti.isString)(message))
this._channel.sendToServer({ message, isBase64: false }).catch(() => {
});
else
this._channel.sendToServer({ message: message.toString("base64"), isBase64: true }).catch(() => {
});
},
async [Symbol.asyncDispose]() {
await this.close();
}
};
this._channel.on('messageFromPage', ({
message,
isBase64
}) => {
if (this._onPageMessage) this._onPageMessage(isBase64 ? Buffer.from(message, 'base64') : message);else if (this._connected) this._channel.sendToServer({
message,
isBase64
}).catch(() => {});
this._channel.on("messageFromPage", ({ message, isBase64 }) => {
if (this._onPageMessage)
this._onPageMessage(isBase64 ? Buffer.from(message, "base64") : message);
else if (this._connected)
this._channel.sendToServer({ message, isBase64 }).catch(() => {
});
this._channel.on('messageFromServer', ({
message,
isBase64
}) => {
if (this._onServerMessage) this._onServerMessage(isBase64 ? Buffer.from(message, 'base64') : message);else this._channel.sendToPage({
message,
isBase64
}).catch(() => {});
});
this._channel.on('closePage', ({
code,
reason,
wasClean
}) => {
if (this._onPageClose) this._onPageClose(code, reason);else this._channel.closeServer({
code,
reason,
wasClean
}).catch(() => {});
this._channel.on("messageFromServer", ({ message, isBase64 }) => {
if (this._onServerMessage)
this._onServerMessage(isBase64 ? Buffer.from(message, "base64") : message);
else
this._channel.sendToPage({ message, isBase64 }).catch(() => {
});
this._channel.on('closeServer', ({
code,
reason,
wasClean
}) => {
if (this._onServerClose) this._onServerClose(code, reason);else this._channel.closePage({
code,
reason,
wasClean
}).catch(() => {});
});
this._channel.on("closePage", ({ code, reason, wasClean }) => {
if (this._onPageClose)
this._onPageClose(code, reason);
else
this._channel.closeServer({ code, reason, wasClean }).catch(() => {
});
});
this._channel.on("closeServer", ({ code, reason, wasClean }) => {
if (this._onServerClose)
this._onServerClose(code, reason);
else
this._channel.closePage({ code, reason, wasClean }).catch(() => {
});
});
}
static from(route) {
return route._object;
}
url() {
return this._initializer.url;
}
async close(options = {}) {
await this._channel.closePage({
...options,
wasClean: true
}).catch(() => {});
await this._channel.closePage({ ...options, wasClean: true }).catch(() => {
});
}
connectToServer() {
if (this._connected) throw new Error('Already connected to the server');
if (this._connected)
throw new Error("Already connected to the server");
this._connected = true;
this._channel.connect().catch(() => {});
this._channel.connect().catch(() => {
});
return this._server;
}
send(message) {
if ((0, _utils.isString)(message)) this._channel.sendToPage({
message,
isBase64: false
}).catch(() => {});else this._channel.sendToPage({
message: message.toString('base64'),
isBase64: true
}).catch(() => {});
if ((0, import_rtti.isString)(message))
this._channel.sendToPage({ message, isBase64: false }).catch(() => {
});
else
this._channel.sendToPage({ message: message.toString("base64"), isBase64: true }).catch(() => {
});
}
onMessage(handler) {
this._onPageMessage = handler;
@@ -453,21 +449,17 @@ class WebSocketRoute extends _channelOwner.ChannelOwner {
onClose(handler) {
this._onPageClose = handler;
}
async [_Symbol$asyncDispose]() {
async [Symbol.asyncDispose]() {
await this.close();
}
async _afterHandle() {
if (this._connected) return;
// Ensure that websocket is "open" and can send messages without an actual server connection.
if (this._connected)
return;
await this._channel.ensureOpened();
}
}
exports.WebSocketRoute = WebSocketRoute;
class WebSocketRouteHandler {
constructor(baseURL, url, handler) {
this._baseURL = void 0;
this.url = void 0;
this.handler = void 0;
this._baseURL = baseURL;
this.url = url;
this.handler = handler;
@@ -476,20 +468,19 @@ class WebSocketRouteHandler {
const patterns = [];
let all = false;
for (const handler of handlers) {
if ((0, _utils.isString)(handler.url)) patterns.push({
glob: handler.url
});else if ((0, _utils.isRegExp)(handler.url)) patterns.push({
regexSource: handler.url.source,
regexFlags: handler.url.flags
});else all = true;
if ((0, import_rtti.isString)(handler.url))
patterns.push({ glob: handler.url });
else if ((0, import_rtti.isRegExp)(handler.url))
patterns.push({ regexSource: handler.url.source, regexFlags: handler.url.flags });
else
all = true;
}
if (all) return [{
glob: '**/*'
}];
if (all)
return [{ glob: "**/*" }];
return patterns;
}
matches(wsURL) {
return (0, _utils.urlMatches)(this._baseURL, wsURL, this.url);
return (0, import_urlMatch.urlMatches)(this._baseURL, wsURL, this.url, true);
}
async handle(webSocketRoute) {
const handler = this.handler;
@@ -497,30 +488,25 @@ class WebSocketRouteHandler {
await webSocketRoute._afterHandle();
}
}
exports.WebSocketRouteHandler = WebSocketRouteHandler;
class Response extends _channelOwner.ChannelOwner {
class Response extends import_channelOwner.ChannelOwner {
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._finishedPromise = new import_manualPromise.ManualPromise();
this.markAsInternalType();
this._provisionalHeaders = new RawHeaders(initializer.headers);
this._request = Request.from(this._initializer.request);
Object.assign(this._request._timing, this._initializer.timing);
}
static from(response) {
return response._object;
}
static fromNullable(response) {
return response ? Response.from(response) : null;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._provisionalHeaders = void 0;
this._actualHeadersPromise = void 0;
this._request = void 0;
this._finishedPromise = new _manualPromise.ManualPromise();
this.markAsInternalType();
this._provisionalHeaders = new RawHeaders(initializer.headers);
this._request = Request.from(this._initializer.request);
Object.assign(this._request._timing, this._initializer.timing);
}
url() {
return this._initializer.url;
}
ok() {
// Status 0 is for file:// URLs
return this._initializer.status === 0 || this._initializer.status >= 200 && this._initializer.status <= 299;
}
status() {
@@ -532,7 +518,6 @@ class Response extends _channelOwner.ChannelOwner {
fromServiceWorker() {
return this._initializer.fromServiceWorker;
}
/**
* @deprecated
*/
@@ -567,7 +552,7 @@ class Response extends _channelOwner.ChannelOwner {
}
async text() {
const content = await this.body();
return content.toString('utf8');
return content.toString("utf8");
}
async json() {
const content = await this.text();
@@ -586,37 +571,30 @@ class Response extends _channelOwner.ChannelOwner {
return (await this._channel.securityDetails()).value || null;
}
}
exports.Response = Response;
class WebSocket extends _channelOwner.ChannelOwner {
class WebSocket extends import_channelOwner.ChannelOwner {
static from(webSocket) {
return webSocket._object;
}
constructor(parent, type, guid, initializer) {
super(parent, type, guid, initializer);
this._page = void 0;
this._isClosed = void 0;
this._isClosed = false;
this._page = parent;
this._channel.on('frameSent', event => {
if (event.opcode === 1) this.emit(_events.Events.WebSocket.FrameSent, {
payload: event.data
});else if (event.opcode === 2) this.emit(_events.Events.WebSocket.FrameSent, {
payload: Buffer.from(event.data, 'base64')
this._channel.on("frameSent", (event) => {
if (event.opcode === 1)
this.emit(import_events.Events.WebSocket.FrameSent, { payload: event.data });
else if (event.opcode === 2)
this.emit(import_events.Events.WebSocket.FrameSent, { payload: Buffer.from(event.data, "base64") });
});
this._channel.on("frameReceived", (event) => {
if (event.opcode === 1)
this.emit(import_events.Events.WebSocket.FrameReceived, { payload: event.data });
else if (event.opcode === 2)
this.emit(import_events.Events.WebSocket.FrameReceived, { payload: Buffer.from(event.data, "base64") });
});
this._channel.on('frameReceived', event => {
if (event.opcode === 1) this.emit(_events.Events.WebSocket.FrameReceived, {
payload: event.data
});else if (event.opcode === 2) this.emit(_events.Events.WebSocket.FrameReceived, {
payload: Buffer.from(event.data, 'base64')
});
});
this._channel.on('socketError', ({
error
}) => this.emit(_events.Events.WebSocket.Error, error));
this._channel.on('close', () => {
this._channel.on("socketError", ({ error }) => this.emit(import_events.Events.WebSocket.Error, error));
this._channel.on("close", () => {
this._isClosed = true;
this.emit(_events.Events.WebSocket.Close, this);
this.emit(import_events.Events.WebSocket.Close, this);
});
}
url() {
@@ -627,79 +605,72 @@ class WebSocket extends _channelOwner.ChannelOwner {
}
async waitForEvent(event, optionsOrPredicate = {}) {
return await this._wrapApiCall(async () => {
const timeout = this._page._timeoutSettings.timeout(typeof optionsOrPredicate === 'function' ? {} : optionsOrPredicate);
const predicate = typeof optionsOrPredicate === 'function' ? optionsOrPredicate : optionsOrPredicate.predicate;
const waiter = _waiter.Waiter.createForEvent(this, event);
const timeout = this._page._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 !== _events.Events.WebSocket.Error) waiter.rejectOnEvent(this, _events.Events.WebSocket.Error, new Error('Socket error'));
if (event !== _events.Events.WebSocket.Close) waiter.rejectOnEvent(this, _events.Events.WebSocket.Close, new Error('Socket closed'));
waiter.rejectOnEvent(this._page, _events.Events.Page.Close, () => this._page._closeErrorWithReason());
if (event !== import_events.Events.WebSocket.Error)
waiter.rejectOnEvent(this, import_events.Events.WebSocket.Error, new Error("Socket error"));
if (event !== import_events.Events.WebSocket.Close)
waiter.rejectOnEvent(this, import_events.Events.WebSocket.Close, new Error("Socket closed"));
waiter.rejectOnEvent(this._page, import_events.Events.Page.Close, () => this._page._closeErrorWithReason());
const result = await waiter.waitForEvent(this, event, predicate);
waiter.dispose();
return result;
});
}
}
exports.WebSocket = WebSocket;
function validateHeaders(headers) {
for (const key of Object.keys(headers)) {
const value = headers[key];
if (!Object.is(value, undefined) && !(0, _utils.isString)(value)) throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
if (!Object.is(value, void 0) && !(0, import_rtti.isString)(value))
throw new Error(`Expected value of header "${key}" to be String, but "${typeof value}" is found.`);
}
}
class RouteHandler {
constructor(baseURL, url, handler, times = Number.MAX_SAFE_INTEGER) {
constructor(platform, baseURL, url, handler, times = Number.MAX_SAFE_INTEGER) {
this.handledCount = 0;
this._baseURL = void 0;
this._times = void 0;
this.url = void 0;
this.handler = void 0;
this._ignoreException = false;
this._activeInvocations = new Set();
this._svedZone = void 0;
this._activeInvocations = /* @__PURE__ */ new Set();
this._baseURL = baseURL;
this._times = times;
this.url = url;
this.handler = handler;
this._svedZone = _utils.zones.current().without('apiZone');
this._savedZone = platform.zones.current().pop();
}
static prepareInterceptionPatterns(handlers) {
const patterns = [];
let all = false;
for (const handler of handlers) {
if ((0, _utils.isString)(handler.url)) patterns.push({
glob: handler.url
});else if ((0, _utils.isRegExp)(handler.url)) patterns.push({
regexSource: handler.url.source,
regexFlags: handler.url.flags
});else all = true;
if ((0, import_rtti.isString)(handler.url))
patterns.push({ glob: handler.url });
else if ((0, import_rtti.isRegExp)(handler.url))
patterns.push({ regexSource: handler.url.source, regexFlags: handler.url.flags });
else
all = true;
}
if (all) return [{
glob: '**/*'
}];
if (all)
return [{ glob: "**/*" }];
return patterns;
}
matches(requestURL) {
return (0, _utils.urlMatches)(this._baseURL, requestURL, this.url);
return (0, import_urlMatch.urlMatches)(this._baseURL, requestURL, this.url);
}
async handle(route) {
return await this._svedZone.run(async () => this._handleImpl(route));
return await this._savedZone.run(async () => this._handleImpl(route));
}
async _handleImpl(route) {
const handlerInvocation = {
complete: new _manualPromise.ManualPromise(),
route
};
const handlerInvocation = { complete: new import_manualPromise.ManualPromise(), route };
this._activeInvocations.add(handlerInvocation);
try {
return await this._handleInternal(route);
} catch (e) {
// If the handler was stopped (without waiting for completion), we ignore all exceptions.
if (this._ignoreException) return false;
if ((0, _errors.isTargetClosedError)(e)) {
// We are failing in the handler because the target close closed.
// Give user a hint!
(0, _utils.rewriteErrorMessage)(e, `"${e.message}" while running route callback.\nConsider awaiting \`await page.unrouteAll({ behavior: 'ignoreErrors' })\`\nbefore the end of the test to ignore remaining routes in flight.`);
if (this._ignoreException)
return false;
if ((0, import_errors.isTargetClosedError)(e)) {
(0, import_stackTrace.rewriteErrorMessage)(e, `"${e.message}" while running route callback.
Consider awaiting \`await page.unrouteAll({ behavior: 'ignoreErrors' })\`
before the end of the test to ignore remaining routes in flight.`);
}
throw e;
} finally {
@@ -708,16 +679,13 @@ class RouteHandler {
}
}
async stop(behavior) {
// When a handler is manually unrouted or its page/context is closed we either
// - wait for the current handler invocations to finish
// - or do not wait, if the user opted out of it, but swallow all exceptions
// that happen after the unroute/close.
if (behavior === 'ignoreErrors') {
if (behavior === "ignoreErrors") {
this._ignoreException = true;
} else {
const promises = [];
for (const activation of this._activeInvocations) {
if (!activation.route._didThrow) promises.push(activation.complete);
if (!activation.route._didThrow)
promises.push(activation.complete);
}
await Promise.all(promises);
}
@@ -725,45 +693,59 @@ class RouteHandler {
async _handleInternal(route) {
++this.handledCount;
const handledPromise = route._startHandling();
// Extract handler into a variable to avoid [RouteHandler.handler] in the stack.
const handler = this.handler;
const [handled] = await Promise.all([handledPromise, handler(route, route.request())]);
const [handled] = await Promise.all([
handledPromise,
handler(route, route.request())
]);
return handled;
}
willExpire() {
return this.handledCount + 1 >= this._times;
}
}
exports.RouteHandler = RouteHandler;
class RawHeaders {
constructor(headers) {
this._headersMap = new import_multimap.MultiMap();
this._headersArray = headers;
for (const header of headers)
this._headersMap.set(header.name.toLowerCase(), header.value);
}
static _fromHeadersObjectLossy(headers) {
const headersArray = Object.entries(headers).map(([name, value]) => ({
name,
value
})).filter(header => header.value !== undefined);
})).filter((header) => header.value !== void 0);
return new RawHeaders(headersArray);
}
constructor(headers) {
this._headersArray = void 0;
this._headersMap = new _utils.MultiMap();
this._headersArray = headers;
for (const header of headers) this._headersMap.set(header.name.toLowerCase(), header.value);
}
get(name) {
const values = this.getAll(name);
if (!values || !values.length) return null;
return values.join(name.toLowerCase() === 'set-cookie' ? '\n' : ', ');
if (!values || !values.length)
return null;
return values.join(name.toLowerCase() === "set-cookie" ? "\n" : ", ");
}
getAll(name) {
return [...this._headersMap.get(name.toLowerCase())];
}
headers() {
const result = {};
for (const name of this._headersMap.keys()) result[name] = this.get(name);
for (const name of this._headersMap.keys())
result[name] = this.get(name);
return result;
}
headersArray() {
return this._headersArray;
}
}
exports.RawHeaders = RawHeaders;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
RawHeaders,
Request,
Response,
Route,
RouteHandler,
WebSocket,
WebSocketRoute,
WebSocketRouteHandler,
validateHeaders
});

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