Compare commits

...

35 Commits
0.8.1 ... 0.9.1

Author SHA1 Message Date
undergroundwires
229c13a195 improve explanation for selections 2021-01-23 06:06:25 +01:00
undergroundwires
d7f9ef1cbe fix node APIs no longer working on desktop nklayman/vue-cli-plugin-electron-builder#610, nklayman/vue-cli-plugin-electron-builder#742 2021-01-22 06:07:30 +01:00
undergroundwires
7930bef48c transpile using babel for legacy browser support 2021-01-21 04:38:02 +01:00
undergroundwires
8b0e47da38 fix selection state indicator on cards not showing up 2021-01-20 05:30:19 +01:00
undergroundwires
2316e3fb68 specify desktop publish targets as defaults (may) change 2021-01-19 06:51:09 +01:00
undergroundwires
4015e2ccd8 in CI/CD, publish packages for other OSes if single one fails 2021-01-17 07:14:23 +01:00
undergroundwires
cf907d029a in CI/CD, allow publishing to github if release is more than 2 hours old electron-userland/electron-builder#2074 2021-01-16 08:14:38 +01:00
undergroundwires-bot
79a6c8b2ef ⬆️ bump everywhere to 0.9.0 2021-01-15 03:26:07 +00:00
undergroundwires
c318bd301a update screenshot 2021-01-15 04:25:04 +01:00
undergroundwires
86a2b2fda0 add scripts to manage chromium based edge 2021-01-14 05:23:46 +01:00
undergroundwires
8a8b7319d5 add initial macOS support #40 2021-01-13 16:31:20 +01:00
undergroundwires
2428de23ee fix unintended null file creation #52 2021-01-12 04:13:51 +01:00
undergroundwires
7ec889e759 recommend removing cortana taskbar icon on standard mode 2021-01-11 03:09:31 +01:00
undergroundwires
9d009c40dd document app connector removal and recommend on strict mode 2021-01-11 03:05:15 +01:00
undergroundwires
663d63bde0 recommend onedrive removal on strict mode 2021-01-10 01:35:30 +01:00
undergroundwires
6b83dcbf8f move application.yaml to collections/windows.yaml #40 2021-01-09 02:02:16 +01:00
undergroundwires
72e925fb6f improve uninstalling apps to show errors and exit if taking ownership fails #51 2021-01-08 01:16:58 +01:00
undergroundwires
c299e95bc6 fix typo causing uninstalling capabilities to fail #51 2021-01-07 00:59:00 +01:00
undergroundwires
2e40605d59 refactor to allow switching ICategoryCollection context #40 2021-01-05 22:28:38 +01:00
undergroundwires
3455a2ca6c add script to clean previous windows installation #35 2021-01-04 00:35:45 +01:00
undergroundwires
6fe858d86a rename Application to CategoryCollection #40 2021-01-02 17:50:47 +01:00
undergroundwires
7cc161c828 rework Cortana scripts to remove duplicates, better document and support Windows version 2004/2009 #43 2021-01-01 23:23:55 +01:00
undergroundwires
e14bf2bfa0 add scripts to prevent family safety monitoring 2020-12-30 19:49:38 +01:00
undergroundwires
34672414c3 refactor folders to move "/state" (IApplicationState) inside "/context" (IApplicationContext) 2020-12-29 05:45:03 +01:00
undergroundwires
f7557bcc0f refactor application.yaml to become an os definition #40 2020-12-27 18:14:38 +01:00
undergroundwires-bot
e4b6cdfb18 ⬆️ bump everywhere to 0.8.2 2020-12-27 14:42:30 +00:00
undergroundwires
8cd3352017 rename "disable" to "uninstall" for removing capabilities #47 2020-12-26 16:13:50 +01:00
undergroundwires
c4ec6a1445 refactor capabilities to use a shared function #41 #47 2020-12-25 22:38:06 +01:00
undergroundwires
b3117c27f2 rename app launch tracking tweak to make it more clear #44 2020-12-24 23:39:47 +01:00
undergroundwires
54ba4dbb0b in ci/cd, do not run security checks if PRs do not change dependencies #48 2020-12-23 22:42:30 +01:00
Marc05
a744415eb2 correct typos (#48)
Co-authored-by: Marc05 <git@marc05.net>
2020-12-23 22:23:42 +01:00
undergroundwires
55f936fee9 fix type assignment error after typescript upgrade 2020-12-22 23:12:48 +01:00
undergroundwires
d9e44e2574 update dependencies to latest #46 2020-12-21 22:20:49 +01:00
greaterthanstar
52d4313156 replace ampersand in "Movies & TV app" with "and" to prevent batch file from misinterpreting it (#45) 2020-12-20 21:48:52 +01:00
undergroundwires-bot
c2b531e968 ⬆️ bump everywhere to 0.8.1 2020-11-16 18:38:23 +00:00
170 changed files with 9668 additions and 4464 deletions

View File

@@ -10,6 +10,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [macos, ubuntu, windows] os: [macos, ubuntu, windows]
fail-fast: false # So publish runs for other OSes if one fails
runs-on: ${{ matrix.os }}-latest runs-on: ${{ matrix.os }}-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -30,3 +31,4 @@ jobs:
run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github run: npm run electron:build -- -p always # https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/recipes.html#upload-release-to-github
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EP_GH_IGNORE_TIME: true # Otherwise publishing fails if GitHub release is more than 2 hours old https://github.com/electron-userland/electron-builder/issues/2074

View File

@@ -3,6 +3,7 @@ name: Security checks
on: on:
push: push:
pull_request: pull_request:
paths: [ '/package.json', '/package-lock.json' ] # Allow PRs to be green if they do not introduce dependency change
schedule: schedule:
- cron: '0 0 * * 0' - cron: '0 0 * * 0'

View File

@@ -1,200 +1,252 @@
# Changelog # Changelog
## 0.9.0 (2021-01-15)
* refactor application.yaml to become an os definition #40 | [f7557bc](https://github.com/undergroundwires/privacy.sexy/commit/f7557bcc0faf44e8395b68c7eb14c5f715f07b92)
* refactor folders to move "/state" (IApplicationState) inside "/context" (IApplicationContext) | [3467241](https://github.com/undergroundwires/privacy.sexy/commit/34672414c3e0757173036e351df0a73c1708ded5)
* add scripts to prevent family safety monitoring | [e14bf2b](https://github.com/undergroundwires/privacy.sexy/commit/e14bf2bfa03efe28ff39942c9891fca605f13eed)
* rework Cortana scripts to remove duplicates, better document and support Windows version 2004/2009 #43 | [7cc161c](https://github.com/undergroundwires/privacy.sexy/commit/7cc161c828a3fa49f6f254e31834a95a502b7aa2)
* rename Application to CategoryCollection #40 | [6fe858d](https://github.com/undergroundwires/privacy.sexy/commit/6fe858d86aeb0f8b6d5ae5c2a5e3c25ff32e5f6f)
* add script to clean previous windows installation #35 | [3455a2c](https://github.com/undergroundwires/privacy.sexy/commit/3455a2ca6ce13f9b0e866d88532a5c3d6de30d4d)
* refactor to allow switching ICategoryCollection context #40 | [2e40605](https://github.com/undergroundwires/privacy.sexy/commit/2e40605d59eb764768457c6af561487e7ff09777)
* fix typo causing uninstalling capabilities to fail #51 | [c299e95](https://github.com/undergroundwires/privacy.sexy/commit/c299e95bc6d588317b69a9efcf5752ff5c9c3926)
* improve uninstalling apps to show errors and exit if taking ownership fails #51 | [72e925f](https://github.com/undergroundwires/privacy.sexy/commit/72e925fb6f908cd58fb50618f29726b3fb54a7f1)
* move application.yaml to collections/windows.yaml #40 | [6b83dcb](https://github.com/undergroundwires/privacy.sexy/commit/6b83dcbf8fa08b4efe9974c7d7a667458f7c595c)
* recommend onedrive removal on strict mode | [663d63b](https://github.com/undergroundwires/privacy.sexy/commit/663d63bde08dd1b0d43ec144c758399cec90ec70)
* document app connector removal and recommend on strict mode | [9d009c4](https://github.com/undergroundwires/privacy.sexy/commit/9d009c40dd411c73c7ae032a78ec51490ecce024)
* recommend removing cortana taskbar icon on standard mode | [7ec889e](https://github.com/undergroundwires/privacy.sexy/commit/7ec889e759df04bba99d3b6c4d0597809bd94058)
* fix unintended null file creation #52 | [2428de2](https://github.com/undergroundwires/privacy.sexy/commit/2428de23ee02de987e7e6ec80ebd67be369d9048)
* add initial macOS support #40 | [8a8b731](https://github.com/undergroundwires/privacy.sexy/commit/8a8b7319d539b31c1d8ad9eaf541762d64f02493)
* add scripts to manage chromium based edge | [86a2b2f](https://github.com/undergroundwires/privacy.sexy/commit/86a2b2fda0b6a2565c550758c7c175fa795926b7)
* update screenshot | [c318bd3](https://github.com/undergroundwires/privacy.sexy/commit/c318bd301a2cbebbf5cdba06c0f18ac291aa4788)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.8.2...0.9.0)
## 0.8.2 (2020-12-26)
* replace ampersand in "Movies & TV app" with "and" to prevent batch file from misinterpreting it (#45) | [52d4313](https://github.com/undergroundwires/privacy.sexy/commit/52d4313156d2dcbc508b7271e7d9dfd45723d7bc)
* update dependencies to latest #46 | [d9e44e2](https://github.com/undergroundwires/privacy.sexy/commit/d9e44e25744e5d0aa01b8fc0f0af74c48027aea3)
* fix type assignment error after typescript upgrade | [55f936f](https://github.com/undergroundwires/privacy.sexy/commit/55f936fee9f86757f63fa8952d89711feb247e5b)
* correct typos (#48) | [a744415](https://github.com/undergroundwires/privacy.sexy/commit/a744415eb2ab65ee4f519f863fdd6a43953377bb)
* in ci/cd, do not run security checks if PRs do not change dependencies #48 | [54ba4db](https://github.com/undergroundwires/privacy.sexy/commit/54ba4dbb0bf8f08f9479f8facb2e12c786c1bc51)
* rename app launch tracking tweak to make it more clear #44 | [b3117c2](https://github.com/undergroundwires/privacy.sexy/commit/b3117c27f283c2d5a25fd94021a9f628a272cda6)
* refactor capabilities to use a shared function #41 #47 | [c4ec6a1](https://github.com/undergroundwires/privacy.sexy/commit/c4ec6a1445d2fd5eb923c97b54aee01e272e13a8)
* rename "disable" to "uninstall" for removing capabilities #47 | [8cd3352](https://github.com/undergroundwires/privacy.sexy/commit/8cd3352017f9dc85f8efcd7b450d90f555d3e92e)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.8.1...0.8.2)
## 0.8.1 (2020-11-16)
* refactor removing bloatware to use functions #41 | [ffa279f](https://github.com/undergroundwires/privacy.sexy/commit/ffa279f3dfe51db564f0a3859543eb212170e173)
* fix reinstalling store apps by searching appx for all users | [2c5ab3e](https://github.com/undergroundwires/privacy.sexy/commit/2c5ab3ea7da159cfb9fbfbbb7cdd28afbee965ea)
* fix clearing jump lists causing os to break and user pin removal #37 | [92c3dd9](https://github.com/undergroundwires/privacy.sexy/commit/92c3dd923257ac940eab6cbab858698ed55a09b7)
* fix reinstalling store apps by searching appx for all users | [4e72673](https://github.com/undergroundwires/privacy.sexy/commit/4e7267337301fe4a0480ba0603218fca25c2d096)
* refactor unused imports | [45b8dd9](https://github.com/undergroundwires/privacy.sexy/commit/45b8dd972b1edf9e263858c23b27e7a1d2e07077)
* fix not being able to uninstall system apps | [31e08d2](https://github.com/undergroundwires/privacy.sexy/commit/31e08d231d52e2a691400468b7c599c142a29448)
* fix wrong app names caused by wrong Microsoft docs | [e41e40c](https://github.com/undergroundwires/privacy.sexy/commit/e41e40c5bf01e2971d3054fcd3a48f8465a96622)
* unrecommend some system apps and document more | [29c7704](https://github.com/undergroundwires/privacy.sexy/commit/29c7704e0bd38f6e9923cde84accb569b02d2dd6)
* fix not being able to rename paths including brackets | [ad1872e](https://github.com/undergroundwires/privacy.sexy/commit/ad1872e7cd4ad7ef9facf33fadfa8c6a55065dd3)
* fix errors when file already exists | [c26bc20](https://github.com/undergroundwires/privacy.sexy/commit/c26bc209eb167aa71cad10b7f3ea02d0dedd97b0)
* move Microsoft.Appconnector to right category | [b247b12](https://github.com/undergroundwires/privacy.sexy/commit/b247b12c3f009aab4350e33f4779fd193e570050)
* replace deprecated github ::set-env command | [ab7d617](https://github.com/undergroundwires/privacy.sexy/commit/ab7d617886a65fe4f3c2daa929168e5678ccae60)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.8.0...0.8.1)
## 0.8.0 (2020-11-01) ## 0.8.0 (2020-11-01)
* add support for different recommendation levels: strict and standard | [commit](https://github.com/undergroundwires/privacy.sexy/commit/14be3017c55ed5e0d9bdecb63fcc4e1131e79ab0) * add support for different recommendation levels: strict and standard | [14be301](https://github.com/undergroundwires/privacy.sexy/commit/14be3017c55ed5e0d9bdecb63fcc4e1131e79ab0)
* Add GroupMe and Spotify removal option (#34) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3785c623f837b182d82fa383dfe7709722a67248) * Add GroupMe and Spotify removal option (#34) | [3785c62](https://github.com/undergroundwires/privacy.sexy/commit/3785c623f837b182d82fa383dfe7709722a67248)
* switch places of download and copy buttons | [commit](https://github.com/undergroundwires/privacy.sexy/commit/50fb29038ae19b17ec006093db02cf1e568d53c3) * switch places of download and copy buttons | [50fb290](https://github.com/undergroundwires/privacy.sexy/commit/50fb29038ae19b17ec006093db02cf1e568d53c3)
* change "download" button to "save" on desktop | [commit](https://github.com/undergroundwires/privacy.sexy/commit/07fc555324d8bf4fa3594a9701daaa124a873153) * change "download" button to "save" on desktop | [07fc555](https://github.com/undergroundwires/privacy.sexy/commit/07fc555324d8bf4fa3594a9701daaa124a873153)
* show icons on cards during indeterminate and fully selected states | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1072505219edc47d82a91f148d1f310f32869fea) * show icons on cards during indeterminate and fully selected states | [1072505](https://github.com/undergroundwires/privacy.sexy/commit/1072505219edc47d82a91f148d1f310f32869fea)
* add scripts to increase cryptography, enable camera notifications and remove todo app (#36) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4c68408f1ec339dc8d39c7ab044f825a7f7185cb) * add scripts to increase cryptography, enable camera notifications and remove todo app (#36) | [4c68408](https://github.com/undergroundwires/privacy.sexy/commit/4c68408f1ec339dc8d39c7ab044f825a7f7185cb)
* update recommendations to be safer and consistent | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d0019c2c9b1eea620e2e8e02b586903ce62b80e3) * update recommendations to be safer and consistent | [d0019c2](https://github.com/undergroundwires/privacy.sexy/commit/d0019c2c9b1eea620e2e8e02b586903ce62b80e3)
* rework disabling metadata retrieval | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ac70b063b8a15bc528256185792939685be6b36f) * rework disabling metadata retrieval | [ac70b06](https://github.com/undergroundwires/privacy.sexy/commit/ac70b063b8a15bc528256185792939685be6b36f)
* add all dist folders in gitignore because of files auto-generated by vscode | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1a9db31c7778c3269a71c0bd9665827efda70a02) * add all dist folders in gitignore because of files auto-generated by vscode | [1a9db31](https://github.com/undergroundwires/privacy.sexy/commit/1a9db31c7778c3269a71c0bd9665827efda70a02)
* add support for shared functions #41 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8ce06facbd54184402a4b1af3c7303e64db85b8a) * add support for shared functions #41 | [8ce06fa](https://github.com/undergroundwires/privacy.sexy/commit/8ce06facbd54184402a4b1af3c7303e64db85b8a)
* hide scrollbars on code area when not overflowing | [commit](https://github.com/undergroundwires/privacy.sexy/commit/fd28eaad061c75ea1aa7e0f0d60ea37a7e52f8c4) * hide scrollbars on code area when not overflowing | [fd28eaa](https://github.com/undergroundwires/privacy.sexy/commit/fd28eaad061c75ea1aa7e0f0d60ea37a7e52f8c4)
* update screenshot | [commit](https://github.com/undergroundwires/privacy.sexy/commit/cfedcd724cad7708b30c7390a7bca3b6313b6726) * update screenshot | [cfedcd7](https://github.com/undergroundwires/privacy.sexy/commit/cfedcd724cad7708b30c7390a7bca3b6313b6726)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.6...0.8.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.6...0.8.0)
## 0.7.6 (2020-10-18) ## 0.7.6 (2020-10-18)
* add docs for default0 pointing to github discussion (#30) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a3fc3782efd346b4c99d2a0b40df2eb0229f5b36) * add docs for default0 pointing to github discussion (#30) | [a3fc378](https://github.com/undergroundwires/privacy.sexy/commit/a3fc3782efd346b4c99d2a0b40df2eb0229f5b36)
* add robots.txt to explicitly allow indexing | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4c2f74949b0758d33049bdfa4f0124a28958f8ea) * add robots.txt to explicitly allow indexing | [4c2f749](https://github.com/undergroundwires/privacy.sexy/commit/4c2f74949b0758d33049bdfa4f0124a28958f8ea)
* add more reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/19a092dd31fb3588277f1ab3120b409d98506752) * add more reversibility | [19a092d](https://github.com/undergroundwires/privacy.sexy/commit/19a092dd31fb3588277f1ab3120b409d98506752)
* refactor to read more from package.json | [commit](https://github.com/undergroundwires/privacy.sexy/commit/784a67afff681bc19147d03c947de0e165d97e87) * refactor to read more from package.json | [784a67a](https://github.com/undergroundwires/privacy.sexy/commit/784a67afff681bc19147d03c947de0e165d97e87)
* simplify "why" section | [commit](https://github.com/undergroundwires/privacy.sexy/commit/77c3d2bbb8d13db86bb82ed0b5cbeaacfdea3db9) * simplify "why" section | [77c3d2b](https://github.com/undergroundwires/privacy.sexy/commit/77c3d2bbb8d13db86bb82ed0b5cbeaacfdea3db9)
* update dependencies to latest | [commit](https://github.com/undergroundwires/privacy.sexy/commit/11e06131655398db08faeeacff62062e46e0dddd) * update dependencies to latest | [11e0613](https://github.com/undergroundwires/privacy.sexy/commit/11e06131655398db08faeeacff62062e46e0dddd)
* run tests on all operating systems: macos, ubuntu, windows | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d9d7f62d81d4d8f95104d33211e82641884d711f) * run tests on all operating systems: macos, ubuntu, windows | [d9d7f62](https://github.com/undergroundwires/privacy.sexy/commit/d9d7f62d81d4d8f95104d33211e82641884d711f)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.5...0.7.6) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.5...0.7.6)
## 0.7.5 (2020-09-14) ## 0.7.5 (2020-09-14)
* fix reverting (reinstalling) capabilities not working | [commit](https://github.com/undergroundwires/privacy.sexy/commit/939d838e3535bb1c9b00c8ea9dacb735ae41d700) * fix reverting (reinstalling) capabilities not working | [939d838](https://github.com/undergroundwires/privacy.sexy/commit/939d838e3535bb1c9b00c8ea9dacb735ae41d700)
* fix tests and checks are not running on PRs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/82d509129b4e4a5df4b84786a0d6842a7d26e888) * fix tests and checks are not running on PRs | [82d5091](https://github.com/undergroundwires/privacy.sexy/commit/82d509129b4e4a5df4b84786a0d6842a7d26e888)
* fix the recycling bin option (#32) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/15db3118012a172a2191a2afad57084a65b34642) * fix the recycling bin option (#32) | [15db311](https://github.com/undergroundwires/privacy.sexy/commit/15db3118012a172a2191a2afad57084a65b34642)
* fix rendering issue in older edge/IE | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6efed72bf25c2ddf0901caab7f22966ca13cd47a) * fix rendering issue in older edge/IE | [6efed72](https://github.com/undergroundwires/privacy.sexy/commit/6efed72bf25c2ddf0901caab7f22966ca13cd47a)
* fix pasting in search bar after page load showing no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d1694341578288eeaf8b80caf9296a38d76789f0) * fix pasting in search bar after page load showing no results | [d169434](https://github.com/undergroundwires/privacy.sexy/commit/d1694341578288eeaf8b80caf9296a38d76789f0)
* fix typo | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7dd15ed06433e0e6583ab0fa46a683ce6554bbea) * fix typo | [7dd15ed](https://github.com/undergroundwires/privacy.sexy/commit/7dd15ed06433e0e6583ab0fa46a683ce6554bbea)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.4...0.7.5) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.4...0.7.5)
## 0.7.4 (2020-09-12) ## 0.7.4 (2020-09-12)
* fix checked checkbox has blue border | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4ae385b7fcea9014a68442714b7d99e2ee7df7d0) * fix checked checkbox has blue border | [4ae385b](https://github.com/undergroundwires/privacy.sexy/commit/4ae385b7fcea9014a68442714b7d99e2ee7df7d0)
* fix spectre protection getting single lined #31 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/22b23a9ece446c7f9abd4ede293051eb616ad50a) * fix spectre protection getting single lined #31 | [22b23a9](https://github.com/undergroundwires/privacy.sexy/commit/22b23a9ece446c7f9abd4ede293051eb616ad50a)
* fix missing reg value in denying app access to account | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3c13a9e837e06e097450b31d7eb0c0e6bf20cefb) * fix missing reg value in denying app access to account | [3c13a9e](https://github.com/undergroundwires/privacy.sexy/commit/3c13a9e837e06e097450b31d7eb0c0e6bf20cefb)
* fix wrong path in clear all firefox user profile settings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ee66196d9a60f27d17ae7f62d02b4f119a47e6e0) * fix wrong path in clear all firefox user profile settings | [ee66196](https://github.com/undergroundwires/privacy.sexy/commit/ee66196d9a60f27d17ae7f62d02b4f119a47e6e0)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.3...0.7.4) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.3...0.7.4)
## 0.7.3 (2020-09-12) ## 0.7.3 (2020-09-12)
* fix vscode settings file override and add more configs | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a0d61728ead04b4455437f85820121a848db9e00) * fix vscode settings file override and add more configs | [a0d6172](https://github.com/undergroundwires/privacy.sexy/commit/a0d61728ead04b4455437f85820121a848db9e00)
* fix nvidia tweak error message, categorize and add reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/99a2035fdb0766a4dfc2753133eab0d7666516cd) * fix nvidia tweak error message, categorize and add reversibility | [99a2035](https://github.com/undergroundwires/privacy.sexy/commit/99a2035fdb0766a4dfc2753133eab0d7666516cd)
* improve CPU specific tweaks by conditional platform checks and reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8df5faf4ef05a49da63973bd0fbb5c5d07d5bd93) * improve CPU specific tweaks by conditional platform checks and reversibility | [8df5faf](https://github.com/undergroundwires/privacy.sexy/commit/8df5faf4ef05a49da63973bd0fbb5c5d07d5bd93)
* fix wrong path to the main telemetry file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/de4ac978bdda79573b36d355697b8a028d2c0beb) * fix wrong path to the main telemetry file | [de4ac97](https://github.com/undergroundwires/privacy.sexy/commit/de4ac978bdda79573b36d355697b8a028d2c0beb)
* fix naming of firefox cleanup to mention profiles | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3ab48b1cf5f7f934f07e468ef2318ccee07f530c) * fix naming of firefox cleanup to mention profiles | [3ab48b1](https://github.com/undergroundwires/privacy.sexy/commit/3ab48b1cf5f7f934f07e468ef2318ccee07f530c)
* add reversibility and more scripts to denying app access with better structure | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1d465ee3189d0e5a827453b3f0eb4361efe23770) * add reversibility and more scripts to denying app access with better structure | [1d465ee](https://github.com/undergroundwires/privacy.sexy/commit/1d465ee3189d0e5a827453b3f0eb4361efe23770)
* fix comment lines are being detected as duplicate in validation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b6ccb5927a20412976a54fd2215eb645092f98a8) * fix comment lines are being detected as duplicate in validation | [b6ccb59](https://github.com/undergroundwires/privacy.sexy/commit/b6ccb5927a20412976a54fd2215eb645092f98a8)
* add more detailed error message | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f11c39773c12eccfb3efb898b58c2f6f37ab9ca) * add more detailed error message | [1f11c39](https://github.com/undergroundwires/privacy.sexy/commit/1f11c39773c12eccfb3efb898b58c2f6f37ab9ca)
* fix typo in a test | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f19b2528a69383e63e579d2885f01cd804abf6c) * fix typo in a test | [1f19b25](https://github.com/undergroundwires/privacy.sexy/commit/1f19b2528a69383e63e579d2885f01cd804abf6c)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.2...0.7.3) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.2...0.7.3)
## 0.7.2 (2020-09-06) ## 0.7.2 (2020-09-06)
* update onesync documentation and do not recommend it as it breaks other apps | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f36d8bfc7848bb65ac0c641e318a689bf3816ccf) * update onesync documentation and do not recommend it as it breaks other apps | [f36d8bf](https://github.com/undergroundwires/privacy.sexy/commit/f36d8bfc7848bb65ac0c641e318a689bf3816ccf)
* add reversibility for biometric disabling and do not recommend it | [commit](https://github.com/undergroundwires/privacy.sexy/commit/db74531cd4139615c6d595959217d3651f099019) * add reversibility for biometric disabling and do not recommend it | [db74531](https://github.com/undergroundwires/privacy.sexy/commit/db74531cd4139615c6d595959217d3651f099019)
* fix bad highlighting of selected nodes when using keyboard navigation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/255133af4dfae40171406648a3e2920f16d71cb3) * fix bad highlighting of selected nodes when using keyboard navigation | [255133a](https://github.com/undergroundwires/privacy.sexy/commit/255133af4dfae40171406648a3e2920f16d71cb3)
* add reversibility to removing bloatware | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c7b2a703128470a05f12c9c6e8002444def37ef8) * add reversibility to removing bloatware | [c7b2a70](https://github.com/undergroundwires/privacy.sexy/commit/c7b2a703128470a05f12c9c6e8002444def37ef8)
* fix indeterminate state being lost | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1f266c33535f72b69c65985bf2eff27cd2c5a104) * fix indeterminate state being lost | [1f266c3](https://github.com/undergroundwires/privacy.sexy/commit/1f266c33535f72b69c65985bf2eff27cd2c5a104)
* fix wording in default text in text area | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ca63a0979ef55d07d09d9443e5cea9aa888870a5) * fix wording in default text in text area | [ca63a09](https://github.com/undergroundwires/privacy.sexy/commit/ca63a0979ef55d07d09d9443e5cea9aa888870a5)
* add best practice suggestion to come back | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f4885b6f1c82752f2143934e336d6d1b1af03015) * add best practice suggestion to come back | [f4885b6](https://github.com/undergroundwires/privacy.sexy/commit/f4885b6f1c82752f2143934e336d6d1b1af03015)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.1...0.7.2) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.1...0.7.2)
## 0.7.1 (2020-09-04) ## 0.7.1 (2020-09-04)
* fix some browsers (including firefox) downloading the script as a text file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8c17929151f9c4fa5f48564492bbf400ced95eea) * fix some browsers (including firefox) downloading the script as a text file | [8c17929](https://github.com/undergroundwires/privacy.sexy/commit/8c17929151f9c4fa5f48564492bbf400ced95eea)
* rename screenshot image file | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b8682a852a14ed6cf49986695d9510b840ac9d3d) * rename screenshot image file | [b8682a8](https://github.com/undergroundwires/privacy.sexy/commit/b8682a852a14ed6cf49986695d9510b840ac9d3d)
* fix new/changed script higlighting not working on production builds | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8c38dd73d8c7b77d8d341c0389f4d7229f9b97fd) * fix new/changed script higlighting not working on production builds | [8c38dd7](https://github.com/undergroundwires/privacy.sexy/commit/8c38dd73d8c7b77d8d341c0389f4d7229f9b97fd)
* refactor unused imports | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6badfef9daace0c5de3fd33652a82bfe22261b11) * refactor unused imports | [6badfef](https://github.com/undergroundwires/privacy.sexy/commit/6badfef9daace0c5de3fd33652a82bfe22261b11)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.0...0.7.1) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.7.0...0.7.1)
## 0.7.0 (2020-09-02) ## 0.7.0 (2020-09-02)
* [search] better (multilined) message when there are no results | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec15af01dd020b364c2174fe562fd66227c2320c) * [search] better (multilined) message when there are no results | [ec15af0](https://github.com/undergroundwires/privacy.sexy/commit/ec15af01dd020b364c2174fe562fd66227c2320c)
* [search] added clear/close button | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d6fa9a2a03c0ebe68b94f0b80cc52b4e200c9213) * [search] added clear/close button | [d6fa9a2](https://github.com/undergroundwires/privacy.sexy/commit/d6fa9a2a03c0ebe68b94f0b80cc52b4e200c9213)
* move script generation to /generation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5df458739d076719e350ba194c4f3f772884fcdb) * move script generation to /generation | [5df4587](https://github.com/undergroundwires/privacy.sexy/commit/5df458739d076719e350ba194c4f3f772884fcdb)
* add auto-highlighting of selected/updated code | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b789250cb89e2130b08e1a927df8181cf945dfeb) * add auto-highlighting of selected/updated code | [b789250](https://github.com/undergroundwires/privacy.sexy/commit/b789250cb89e2130b08e1a927df8181cf945dfeb)
* prompt admin priviliges automatically | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f8ba5c46e4923d9c35f200f8a08aa6437f7c0ecc) * prompt admin priviliges automatically | [f8ba5c4](https://github.com/undergroundwires/privacy.sexy/commit/f8ba5c46e4923d9c35f200f8a08aa6437f7c0ecc)
* add removal of ghost (default0) telemetry user | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c262681011f39b4412669b6cf233476f676ca550) * add removal of ghost (default0) telemetry user | [c262681](https://github.com/undergroundwires/privacy.sexy/commit/c262681011f39b4412669b6cf233476f676ca550)
* add more windows defender tweaks, categorization and reversibility | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1a34c7374ba56bafa0209bbb55c81b233bb419ed) * add more windows defender tweaks, categorization and reversibility | [1a34c73](https://github.com/undergroundwires/privacy.sexy/commit/1a34c7374ba56bafa0209bbb55c81b233bb419ed)
* fix NTP script documentation is on wrong place | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3060ebf79cf242370433495cc3e1878b7581b202) * fix NTP script documentation is on wrong place | [3060ebf](https://github.com/undergroundwires/privacy.sexy/commit/3060ebf79cf242370433495cc3e1878b7581b202)
* updated dependencies to latest and audit fixes (#25) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c628aa9aef8ab7c815661d3c1711e7fbc65c69a2) * updated dependencies to latest and audit fixes (#25) | [c628aa9](https://github.com/undergroundwires/privacy.sexy/commit/c628aa9aef8ab7c815661d3c1711e7fbc65c69a2)
* categorize, fix and extend windows log files cleanup | [commit](https://github.com/undergroundwires/privacy.sexy/commit/594a14d6ca76cbd27a21877b8c373c1930589ca6) * categorize, fix and extend windows log files cleanup | [594a14d](https://github.com/undergroundwires/privacy.sexy/commit/594a14d6ca76cbd27a21877b8c373c1930589ca6)
* add more OneDrive cleanup scripts and categorize them | [commit](https://github.com/undergroundwires/privacy.sexy/commit/978d7d08638dd161082f239ed088b12302f29458) * add more OneDrive cleanup scripts and categorize them | [978d7d0](https://github.com/undergroundwires/privacy.sexy/commit/978d7d08638dd161082f239ed088b12302f29458)
* add disabling firefox telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f8b8b4c97ab734d5ba7370894b694993924388da) * add disabling firefox telemetry | [f8b8b4c](https://github.com/undergroundwires/privacy.sexy/commit/f8b8b4c97ab734d5ba7370894b694993924388da)
* add disabling ccleaner telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/018b7e270f207aac926cb12f8069ebfcdce193ce) * add disabling ccleaner telemetry | [018b7e2](https://github.com/undergroundwires/privacy.sexy/commit/018b7e270f207aac926cb12f8069ebfcdce193ce)
* Add disabling of PowerShell 7+ telemetry (#29) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/456e40bedf9afcc846f9b13f1ea144cef6115cf6) * Add disabling of PowerShell 7+ telemetry (#29) | [456e40b](https://github.com/undergroundwires/privacy.sexy/commit/456e40bedf9afcc846f9b13f1ea144cef6115cf6)
* categorize, fix, make scripts reversible in "UI for privacy", "security improvements" and "configure browsers" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/532915b95da9fecd6b981d91bf489359e4e53caa) * categorize, fix, make scripts reversible in "UI for privacy", "security improvements" and "configure browsers" | [532915b](https://github.com/undergroundwires/privacy.sexy/commit/532915b95da9fecd6b981d91bf489359e4e53caa)
* fix "Configure Defender" being in wrong category #28 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f709d6a566ed7846b677b383863deda9680a2a9c) * fix "Configure Defender" being in wrong category #28 | [f709d6a](https://github.com/undergroundwires/privacy.sexy/commit/f709d6a566ed7846b677b383863deda9680a2a9c)
* do not hardcode capability versions and make them reversible | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2afef4ea3d0d3d09aa1fa1eedba8493680bd8f10) * do not hardcode capability versions and make them reversible | [2afef4e](https://github.com/undergroundwires/privacy.sexy/commit/2afef4ea3d0d3d09aa1fa1eedba8493680bd8f10)
* exclude paint, wordpad and notepad from bloatware removal | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d235dee95514a01745aef9479d07f88ffb4b40b8) * exclude paint, wordpad and notepad from bloatware removal | [d235dee](https://github.com/undergroundwires/privacy.sexy/commit/d235dee95514a01745aef9479d07f88ffb4b40b8)
* add reversibility on category level | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f51e8859eeb32c944126d692cfe03a0320c8b568) * add reversibility on category level | [f51e885](https://github.com/undergroundwires/privacy.sexy/commit/f51e8859eeb32c944126d692cfe03a0320c8b568)
* refactor unused imports & variables | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a23d28f2cfa2d64d45460697cf5ee9d6b5920752) * refactor unused imports & variables | [a23d28f](https://github.com/undergroundwires/privacy.sexy/commit/a23d28f2cfa2d64d45460697cf5ee9d6b5920752)
* fix search (got broken in b789250) with tests and refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8bbe6ebf750f1a1cbab493fb99b5ea91f4e21609) * fix search (got broken in b789250) with tests and refactorings | [8bbe6eb](https://github.com/undergroundwires/privacy.sexy/commit/8bbe6ebf750f1a1cbab493fb99b5ea91f4e21609)
* update the screenshot to show off highlighting | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b4aacea2a3e0bbcf2d8a79ff67f51c0f19e888a6) * update the screenshot to show off highlighting | [b4aacea](https://github.com/undergroundwires/privacy.sexy/commit/b4aacea2a3e0bbcf2d8a79ff67f51c0f19e888a6)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.2...0.7.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.2...0.7.0)
## 0.6.2 (2020-08-16) ## 0.6.2 (2020-08-16)
* 🐛 fixed disabling error reporting for november 2019 update | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5967347b80976a519f6f4eb1972a62f3e600df2b) * 🐛 fixed disabling error reporting for november 2019 update | [5967347](https://github.com/undergroundwires/privacy.sexy/commit/5967347b80976a519f6f4eb1972a62f3e600df2b)
* 🐛 fixed blank screen and icons on mac | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7fac0fe79f252e8f9dda4f6f83cd6fa4ba2b539f) * 🐛 fixed blank screen and icons on mac | [7fac0fe](https://github.com/undergroundwires/privacy.sexy/commit/7fac0fe79f252e8f9dda4f6f83cd6fa4ba2b539f)
* 🐛 fixed removing onedrive does not delete scheduled tasks | [commit](https://github.com/undergroundwires/privacy.sexy/commit/b6bfc2572740c0cd46d3bc0058fa767dd5fa862e) * 🐛 fixed removing onedrive does not delete scheduled tasks | [b6bfc25](https://github.com/undergroundwires/privacy.sexy/commit/b6bfc2572740c0cd46d3bc0058fa767dd5fa862e)
* ⚙️ enhanced tweak to disable for office telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/afc3bfb3b8896f332c9a196973ded3dce8fd21e4) * ⚙️ enhanced tweak to disable for office telemetry | [afc3bfb](https://github.com/undergroundwires/privacy.sexy/commit/afc3bfb3b8896f332c9a196973ded3dce8fd21e4)
* ✨ added script to clear dotnet telemery | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1663bfeac7b6580b1335ca5fcf3587b69c080c72) * ✨ added script to clear dotnet telemery | [1663bfe](https://github.com/undergroundwires/privacy.sexy/commit/1663bfeac7b6580b1335ca5fcf3587b69c080c72)
* 🐛 fixed changing time server not working | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c69998c7cb29ffcf40f0af03b73150736581da69) * 🐛 fixed changing time server not working | [c69998c](https://github.com/undergroundwires/privacy.sexy/commit/c69998c7cb29ffcf40f0af03b73150736581da69)
* 🔥 removed disabling ClickToRun as it breaks office | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3d3380f27ebeea53f17f49974aaa89300ffaf2dd) * 🔥 removed disabling ClickToRun as it breaks office | [3d3380f](https://github.com/undergroundwires/privacy.sexy/commit/3d3380f27ebeea53f17f49974aaa89300ffaf2dd)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.1...0.6.2) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.1...0.6.2)
## 0.6.1 (2020-08-09) ## 0.6.1 (2020-08-09)
* updated documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5963d2bac551083f9d16cce6b851abf0e8b88ce7) * updated documentation | [5963d2b](https://github.com/undergroundwires/privacy.sexy/commit/5963d2bac551083f9d16cce6b851abf0e8b88ce7)
* fixed typo in footer | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5c15a7a64aaf24578a32713dec491bf494216303) * fixed typo in footer | [5c15a7a](https://github.com/undergroundwires/privacy.sexy/commit/5c15a7a64aaf24578a32713dec491bf494216303)
* more scripts can be reverted | [commit](https://github.com/undergroundwires/privacy.sexy/commit/831c014f977515454ee6dc664d77a8c434495501) * more scripts can be reverted | [831c014](https://github.com/undergroundwires/privacy.sexy/commit/831c014f977515454ee6dc664d77a8c434495501)
* moved windows connect now to security & recommended | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6049a2b834d8d17af741f8d8f8b07cd15153b001) * moved windows connect now to security & recommended | [6049a2b](https://github.com/undergroundwires/privacy.sexy/commit/6049a2b834d8d17af741f8d8f8b07cd15153b001)
* fixed mac / linux download links | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4c8be45e287b5ea009d6f828f7f327f37850569e) * fixed mac / linux download links | [4c8be45](https://github.com/undergroundwires/privacy.sexy/commit/4c8be45e287b5ea009d6f828f7f327f37850569e)
* tweaks to disable webcam, speech and compatibility telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a5dbe66fc175e39397f296ab2ff703e9b0ab4d7c) * tweaks to disable webcam, speech and compatibility telemetry | [a5dbe66](https://github.com/undergroundwires/privacy.sexy/commit/a5dbe66fc175e39397f296ab2ff703e9b0ab4d7c)
* refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/66d4d39d5bf3db305450514c6b6224654dafbfb2) * refactorings | [66d4d39](https://github.com/undergroundwires/privacy.sexy/commit/66d4d39d5bf3db305450514c6b6224654dafbfb2)
* fixed removing onedrive does not clean start menu / quick access | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1cc12195a3e9a11c590d3ed64d80299b50f74838) * fixed removing onedrive does not clean start menu / quick access | [1cc1219](https://github.com/undergroundwires/privacy.sexy/commit/1cc12195a3e9a11c590d3ed64d80299b50f74838)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.0...0.6.1) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.6.0...0.6.1)
## 0.6.0 (2020-07-26) ## 0.6.0 (2020-07-26)
* fixed dead links in documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/25ce236a7737decaf2eb9b8c29a4c4f34d43f770) * fixed dead links in documentation | [25ce236](https://github.com/undergroundwires/privacy.sexy/commit/25ce236a7737decaf2eb9b8c29a4c4f34d43f770)
* runs tests on each push on the repository | [commit](https://github.com/undergroundwires/privacy.sexy/commit/73c426844a0330718a9ab7de12b61ca05e853323) * runs tests on each push on the repository | [73c4268](https://github.com/undergroundwires/privacy.sexy/commit/73c426844a0330718a9ab7de12b61ca05e853323)
* code area now shows "how" before "why" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4ff4b52202b1c5dbfe2b80580bbe7d93132ab05c) * code area now shows "how" before "why" | [4ff4b52](https://github.com/undergroundwires/privacy.sexy/commit/4ff4b52202b1c5dbfe2b80580bbe7d93132ab05c)
* support for desktop versions #20 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/04b9b59e14766ccd251474ad3710baf1f682fd49) * support for desktop versions #20 | [04b9b59](https://github.com/undergroundwires/privacy.sexy/commit/04b9b59e14766ccd251474ad3710baf1f682fd49)
* reworked on footer & removed github icon | [commit](https://github.com/undergroundwires/privacy.sexy/commit/60a5a2aa4026d384bef9e6a203f1b7514a269c33) * reworked on footer & removed github icon | [60a5a2a](https://github.com/undergroundwires/privacy.sexy/commit/60a5a2aa4026d384bef9e6a203f1b7514a269c33)
* updated dependencies to latest | [commit](https://github.com/undergroundwires/privacy.sexy/commit/45816a2bccb3d11a50e3f2bc19c0a6cc2587deaa) * updated dependencies to latest | [45816a2](https://github.com/undergroundwires/privacy.sexy/commit/45816a2bccb3d11a50e3f2bc19c0a6cc2587deaa)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.5.0...0.6.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.5.0...0.6.0)
## 0.5.0 (2020-07-19) ## 0.5.0 (2020-07-19)
* added ability to revert (#21) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/9c063d59defa6297c64f50b49403e8bd10620de9) * added ability to revert (#21) | [9c063d5](https://github.com/undergroundwires/privacy.sexy/commit/9c063d59defa6297c64f50b49403e8bd10620de9)
* search placeholder shows total scripts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/1d5225de07186f853f4cf7aedd4998f5d00c107a) * search placeholder shows total scripts | [1d5225d](https://github.com/undergroundwires/privacy.sexy/commit/1d5225de07186f853f4cf7aedd4998f5d00c107a)
* do not collapse card when on "Search" and "Select" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/dd7e1416b4df54bf71b719d4654db88769dc0994) * do not collapse card when on "Search" and "Select" | [dd7e141](https://github.com/undergroundwires/privacy.sexy/commit/dd7e1416b4df54bf71b719d4654db88769dc0994)
* opening a card scrolls to its content div | [commit](https://github.com/undergroundwires/privacy.sexy/commit/31d2067f076c3159483baec49975617dddbd158d) * opening a card scrolls to its content div | [31d2067](https://github.com/undergroundwires/privacy.sexy/commit/31d2067f076c3159483baec49975617dddbd158d)
* all cards in same line now have same height | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a9f9e9044385d9aed3b5551fc6c6823e813fd1e5) * all cards in same line now have same height | [a9f9e90](https://github.com/undergroundwires/privacy.sexy/commit/a9f9e9044385d9aed3b5551fc6c6823e813fd1e5)
* patched loadash vulnerability (#18) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/92a7118d1c5013312772e075b9ee5a79c93710b8) * patched loadash vulnerability (#18) | [92a7118](https://github.com/undergroundwires/privacy.sexy/commit/92a7118d1c5013312772e075b9ee5a79c93710b8)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.10...0.5.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.10...0.5.0)
## 0.4.10 (2020-07-15) ## 0.4.10 (2020-07-15)
* fixed script errors & added tests | [commit](https://github.com/undergroundwires/privacy.sexy/commit/9e722ddfb3825fb29d6298025baaaa033120d017) * fixed script errors & added tests | [9e722dd](https://github.com/undergroundwires/privacy.sexy/commit/9e722ddfb3825fb29d6298025baaaa033120d017)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.9...0.4.10) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.9...0.4.10)
## 0.4.9 (2020-07-14) ## 0.4.9 (2020-07-14)
* disable office telemetry Disassembler0/Win10-Initial-Setup-Script#288 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/53cf595e1726ee3de79137fd566978fd512d218f) * disable office telemetry Disassembler0/Win10-Initial-Setup-Script#288 | [53cf595](https://github.com/undergroundwires/privacy.sexy/commit/53cf595e1726ee3de79137fd566978fd512d218f)
* updated to may 2020 update | [commit](https://github.com/undergroundwires/privacy.sexy/commit/909c44d72a4a602ee8f27d06b6ec706c1e432ce1) * updated to may 2020 update | [909c44d](https://github.com/undergroundwires/privacy.sexy/commit/909c44d72a4a602ee8f27d06b6ec706c1e432ce1)
* simplified docker builds | [commit](https://github.com/undergroundwires/privacy.sexy/commit/f27a2871d74e5117fc029be82caef12246e10879) * simplified docker builds | [f27a287](https://github.com/undergroundwires/privacy.sexy/commit/f27a2871d74e5117fc029be82caef12246e10879)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.8...0.4.9) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.8...0.4.9)
## 0.4.8 (2020-07-11) ## 0.4.8 (2020-07-11)
* added more scripts #16 (#17) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d8552c62ffea13ce62abce836c7dd4980eef6bb9) * added more scripts #16 (#17) | [d8552c6](https://github.com/undergroundwires/privacy.sexy/commit/d8552c62ffea13ce62abce836c7dd4980eef6bb9)
* stopping services before disabling #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/628c16eb952495f5b3f6d794161b355f4b08b819) * stopping services before disabling #16 | [628c16e](https://github.com/undergroundwires/privacy.sexy/commit/628c16eb952495f5b3f6d794161b355f4b08b819)
* can disable features, capabilities & remove onedrive #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/30efbcc621eb83dd5a9c1e66b8f1f5350eb95006) * can disable features, capabilities & remove onedrive #16 | [30efbcc](https://github.com/undergroundwires/privacy.sexy/commit/30efbcc621eb83dd5a9c1e66b8f1f5350eb95006)
* updated one more typo (#19) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d7a1325c0b7665ce712dc411965d00fc1d6fa384) * updated one more typo (#19) | [d7a1325](https://github.com/undergroundwires/privacy.sexy/commit/d7a1325c0b7665ce712dc411965d00fc1d6fa384)
* more tweaks #16 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2c4eb78c3f156cb0d033977cffbe7464697680f5) * more tweaks #16 | [2c4eb78](https://github.com/undergroundwires/privacy.sexy/commit/2c4eb78c3f156cb0d033977cffbe7464697680f5)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.7...0.4.8) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.7...0.4.8)
## 0.4.7 (2020-06-30) ## 0.4.7 (2020-06-30)
* removed HKU tweak as all HKU's are changed #10 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c937af8ee7da9aa95131e56abf7bf24800390fe6) * removed HKU tweak as all HKU's are changed #10 | [c937af8](https://github.com/undergroundwires/privacy.sexy/commit/c937af8ee7da9aa95131e56abf7bf24800390fe6)
* Fixed types + script in "Clear Windows log files" (#15) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/461a4f122b342369db5cc08c5e30961c64e68cdd) * Fixed types + script in "Clear Windows log files" (#15) | [461a4f1](https://github.com/undergroundwires/privacy.sexy/commit/461a4f122b342369db5cc08c5e30961c64e68cdd)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.6...0.4.7) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.6...0.4.7)
## 0.4.6 (2020-06-16) ## 0.4.6 (2020-06-16)
* Fixed Some More Issues (#12) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/52d5713a99422cdf900aba819e49e998abac33cc) * Fixed Some More Issues (#12) | [52d5713](https://github.com/undergroundwires/privacy.sexy/commit/52d5713a99422cdf900aba819e49e998abac33cc)
* removed failing continuous deployment #14 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/583c5660d6ac934b845a044e013357aa91f61c15) * removed failing continuous deployment #14 | [583c566](https://github.com/undergroundwires/privacy.sexy/commit/583c5660d6ac934b845a044e013357aa91f61c15)
* Updated Some Tweaks (#11) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0fc18459cde57684f00764815062f838f932aed5) * Updated Some Tweaks (#11) | [0fc1845](https://github.com/undergroundwires/privacy.sexy/commit/0fc18459cde57684f00764815062f838f932aed5)
* Updated Some More Tweaks (#13) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/019b838925e963b7ec052ac76c6faf5650b9eb67) * Updated Some More Tweaks (#13) | [019b838](https://github.com/undergroundwires/privacy.sexy/commit/019b838925e963b7ec052ac76c6faf5650b9eb67)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.5...0.4.6) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.5...0.4.6)
@@ -204,91 +256,91 @@
## 0.4.4 (2020-05-24) ## 0.4.4 (2020-05-24)
* fixed close card button not being visible & cleanup | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0d2efe5b05aa965458b78b8fa43754ce2f4fe11b) * fixed close card button not being visible & cleanup | [0d2efe5](https://github.com/undergroundwires/privacy.sexy/commit/0d2efe5b05aa965458b78b8fa43754ce2f4fe11b)
* new footer with privacy policy | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e2ab124fb799f56ada3570fdc911361cb803e889) * new footer with privacy policy | [e2ab124](https://github.com/undergroundwires/privacy.sexy/commit/e2ab124fb799f56ada3570fdc911361cb803e889)
* one command to lint everything "npm run lint" | [commit](https://github.com/undergroundwires/privacy.sexy/commit/bb98d20637cbf1d524ebb2973e308773006e3153) * one command to lint everything "npm run lint" | [bb98d20](https://github.com/undergroundwires/privacy.sexy/commit/bb98d20637cbf1d524ebb2973e308773006e3153)
* fix "group by" overflows on smaller screens | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c668a97950a1cb7c8bf2a7fd8a72d1101e65e8ce) * fix "group by" overflows on smaller screens | [c668a97](https://github.com/undergroundwires/privacy.sexy/commit/c668a97950a1cb7c8bf2a7fd8a72d1101e65e8ce)
* clicking outside of a card closes it | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aab8f21a8d8dbed54798af581e6e1ad9e86a4be1) * clicking outside of a card closes it | [aab8f21](https://github.com/undergroundwires/privacy.sexy/commit/aab8f21a8d8dbed54798af581e6e1ad9e86a4be1)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.3...0.4.4) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.3...0.4.4)
## 0.4.3 (2020-05-23) ## 0.4.3 (2020-05-23)
* removed redundant documentation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/749a140eb8dba09cb67fec2f8dec937e66e3cff5) * removed redundant documentation | [749a140](https://github.com/undergroundwires/privacy.sexy/commit/749a140eb8dba09cb67fec2f8dec937e66e3cff5)
* fixed broke link | [commit](https://github.com/undergroundwires/privacy.sexy/commit/97b7e03233d9718a8df30cb01ce06ca9489a0295) * fixed broke link | [97b7e03](https://github.com/undergroundwires/privacy.sexy/commit/97b7e03233d9718a8df30cb01ce06ca9489a0295)
* simplified heading | [commit](https://github.com/undergroundwires/privacy.sexy/commit/226074c5342f7463c06fcff1457d352ca30295a3) * simplified heading | [226074c](https://github.com/undergroundwires/privacy.sexy/commit/226074c5342f7463c06fcff1457d352ca30295a3)
* reading version from package.json instead of version file #5 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/691f989682179016ddcbf55a05cded29155288c9) * reading version from package.json instead of version file #5 | [691f989](https://github.com/undergroundwires/privacy.sexy/commit/691f989682179016ddcbf55a05cded29155288c9)
* automatically increases patch number #5 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3e3bc07576f7c7e74e3e11fc7d197cbb9a9fb8c0) * automatically increases patch number #5 | [3e3bc07](https://github.com/undergroundwires/privacy.sexy/commit/3e3bc07576f7c7e74e3e11fc7d197cbb9a9fb8c0)
* using deployment operations from aws-static-site-with-cd | [commit](https://github.com/undergroundwires/privacy.sexy/commit/997be7113f676888892ffa35566d9ebb58a3e9ea) * using deployment operations from aws-static-site-with-cd | [997be71](https://github.com/undergroundwires/privacy.sexy/commit/997be7113f676888892ffa35566d9ebb58a3e9ea)
* automated using bump-everywhere + more quality checks (#8) | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4a91e8ccd8a707bc6bea34ee28cff7fa4f66ee2f) * automated using bump-everywhere + more quality checks (#8) | [4a91e8c](https://github.com/undergroundwires/privacy.sexy/commit/4a91e8ccd8a707bc6bea34ee28cff7fa4f66ee2f)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.2...0.4.3) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.2...0.4.3)
## 0.4.2 (2020-02-29) ## 0.4.2 (2020-02-29)
* added missing semicolon for masking | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e63ac4ae67da68243a525af149ff30e5d485b641) * added missing semicolon for masking | [e63ac4a](https://github.com/undergroundwires/privacy.sexy/commit/e63ac4ae67da68243a525af149ff30e5d485b641)
* set font on input | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0c39a06be5e4b0a2031ad5e9f5220dd669afee53) * set font on input | [0c39a06](https://github.com/undergroundwires/privacy.sexy/commit/0c39a06be5e4b0a2031ad5e9f5220dd669afee53)
* shortened all HKEY paths | [commit](https://github.com/undergroundwires/privacy.sexy/commit/802b36bdd8dcc1f0a2853fe7da2ea2fccd69a88c) * shortened all HKEY paths | [802b36b](https://github.com/undergroundwires/privacy.sexy/commit/802b36bdd8dcc1f0a2853fe7da2ea2fccd69a88c)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.1...0.4.2) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.1...0.4.2)
## 0.4.1 (2020-01-11) ## 0.4.1 (2020-01-11)
* fixed search bug | [commit](https://github.com/undergroundwires/privacy.sexy/commit/31364bdfec503af09ffbb58044a17dfb833fc8d9) * fixed search bug | [31364bd](https://github.com/undergroundwires/privacy.sexy/commit/31364bdfec503af09ffbb58044a17dfb833fc8d9)
* hide grouping while searching | [commit](https://github.com/undergroundwires/privacy.sexy/commit/92f1a36bcb1e1fe7c90efe8ccd3ede55991e9d9c) * hide grouping while searching | [92f1a36](https://github.com/undergroundwires/privacy.sexy/commit/92f1a36bcb1e1fe7c90efe8ccd3ede55991e9d9c)
* 👀🔍 showing search queries | [commit](https://github.com/undergroundwires/privacy.sexy/commit/97a7747933d2b515cc03ab8243e6a8ae702ef16a) * 👀🔍 showing search queries | [97a7747](https://github.com/undergroundwires/privacy.sexy/commit/97a7747933d2b515cc03ab8243e6a8ae702ef16a)
* more efficient queries with single lowercase | [commit](https://github.com/undergroundwires/privacy.sexy/commit/19813b691746d98670823025c460480400e34b6e) * more efficient queries with single lowercase | [19813b6](https://github.com/undergroundwires/privacy.sexy/commit/19813b691746d98670823025c460480400e34b6e)
* using right 🔍 input type | [commit](https://github.com/undergroundwires/privacy.sexy/commit/0ce354ea0956391ad3f37b252daac1127bfc601a) * using right 🔍 input type | [0ce354e](https://github.com/undergroundwires/privacy.sexy/commit/0ce354ea0956391ad3f37b252daac1127bfc601a)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.0...0.4.1) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.4.0...0.4.1)
## 0.4.0 (2020-01-11) ## 0.4.0 (2020-01-11)
* 🔍 support for search | [commit](https://github.com/undergroundwires/privacy.sexy/commit/89862b2775703257b9dc2e19fbebde2c0d0fbda0) * 🔍 support for search | [89862b2](https://github.com/undergroundwires/privacy.sexy/commit/89862b2775703257b9dc2e19fbebde2c0d0fbda0)
* more scripts & better organized | [commit](https://github.com/undergroundwires/privacy.sexy/commit/95baf3175b0d2c7df516f7893a96346b94ac8eca) * more scripts & better organized | [95baf31](https://github.com/undergroundwires/privacy.sexy/commit/95baf3175b0d2c7df516f7893a96346b94ac8eca)
* refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e3f82e069e305f6d94eab335470c8e7b44295dd6) * refactorings | [e3f82e0](https://github.com/undergroundwires/privacy.sexy/commit/e3f82e069e305f6d94eab335470c8e7b44295dd6)
* more margin for the scripts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5ea46ecbf52236953d19f09a8eade08b83e6cd34) * more margin for the scripts | [5ea46ec](https://github.com/undergroundwires/privacy.sexy/commit/5ea46ecbf52236953d19f09a8eade08b83e6cd34)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.3.0...0.4.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.3.0...0.4.0)
## 0.3.0 (2020-01-09) ## 0.3.0 (2020-01-09)
* added description & more descriptive title | [commit](https://github.com/undergroundwires/privacy.sexy/commit/99576340b648550149871e2c0fe0b0d8c2dd0d7c) * added description & more descriptive title | [9957634](https://github.com/undergroundwires/privacy.sexy/commit/99576340b648550149871e2c0fe0b0d8c2dd0d7c)
* allow robots | [commit](https://github.com/undergroundwires/privacy.sexy/commit/eee0e785ec2c5e6bed53d21b4126a57773e35dba) * allow robots | [eee0e78](https://github.com/undergroundwires/privacy.sexy/commit/eee0e785ec2c5e6bed53d21b4126a57773e35dba)
* removed unused references | [commit](https://github.com/undergroundwires/privacy.sexy/commit/cfd888f3afc5c260a0a4a73f2843b86b9f1df2cd) * removed unused references | [cfd888f](https://github.com/undergroundwires/privacy.sexy/commit/cfd888f3afc5c260a0a4a73f2843b86b9f1df2cd)
* 🚫 disable NVIDIA telemetry | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ab28f4ed8538d51e1777c86302a63a0cd9c3cb2a) * 🚫 disable NVIDIA telemetry | [ab28f4e](https://github.com/undergroundwires/privacy.sexy/commit/ab28f4ed8538d51e1777c86302a63a0cd9c3cb2a)
* backwards compatibility for fonts | [commit](https://github.com/undergroundwires/privacy.sexy/commit/4bc13e11926a6df77079646499e799742153b4ab) * backwards compatibility for fonts | [4bc13e1](https://github.com/undergroundwires/privacy.sexy/commit/4bc13e11926a6df77079646499e799742153b4ab)
* added back meta needed for responsiveness | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ed872ef3d9f6c92afc0ce0d06998c60463a8b4e8) * added back meta needed for responsiveness | [ed872ef](https://github.com/undergroundwires/privacy.sexy/commit/ed872ef3d9f6c92afc0ce0d06998c60463a8b4e8)
* fancy-font is renamed to main and now used | [commit](https://github.com/undergroundwires/privacy.sexy/commit/6825001c61426194dc363b96b57a321241f3ba57) * fancy-font is renamed to main and now used | [6825001](https://github.com/undergroundwires/privacy.sexy/commit/6825001c61426194dc363b96b57a321241f3ba57)
* added support for grouping | [commit](https://github.com/undergroundwires/privacy.sexy/commit/ec6b3c54072a77bb4305da1c234db6c649218b88) * added support for grouping | [ec6b3c5](https://github.com/undergroundwires/privacy.sexy/commit/ec6b3c54072a77bb4305da1c234db6c649218b88)
* less hyphens as it looks better on mobile | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e0b080af69157f46ba12e2c25e794f5384671b51) * less hyphens as it looks better on mobile | [e0b080a](https://github.com/undergroundwires/privacy.sexy/commit/e0b080af69157f46ba12e2c25e794f5384671b51)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.2.0...0.3.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.2.0...0.3.0)
## 0.2.0 (2020-01-06) ## 0.2.0 (2020-01-06)
* added GitHub Actions badge for build & deploy | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a229aca68a92bbcd8e8176ac1dd25ce03509e074) * added GitHub Actions badge for build & deploy | [a229aca](https://github.com/undergroundwires/privacy.sexy/commit/a229aca68a92bbcd8e8176ac1dd25ce03509e074)
* more badges 📛🏆📜 | [commit](https://github.com/undergroundwires/privacy.sexy/commit/090e8319091044e53484ba8338510f6fb7c3cb80) * more badges 📛🏆📜 | [090e831](https://github.com/undergroundwires/privacy.sexy/commit/090e8319091044e53484ba8338510f6fb7c3cb80)
* typo fixes + whitespace refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/e99f210c9dcf61a21e445e2a331384b6066f2c98) * typo fixes + whitespace refactorings | [e99f210](https://github.com/undergroundwires/privacy.sexy/commit/e99f210c9dcf61a21e445e2a331384b6066f2c98)
* switched content information to "why" section | [commit](https://github.com/undergroundwires/privacy.sexy/commit/beb3c8339f83a224ca66ad8a911a9265ffe7c9c0) * switched content information to "why" section | [beb3c83](https://github.com/undergroundwires/privacy.sexy/commit/beb3c8339f83a224ca66ad8a911a9265ffe7c9c0)
* fixed contribution URL | [commit](https://github.com/undergroundwires/privacy.sexy/commit/7b4277d7706ccf6ba7e4b7b01aa46f8e3852cfc6) * fixed contribution URL | [7b4277d](https://github.com/undergroundwires/privacy.sexy/commit/7b4277d7706ccf6ba7e4b7b01aa46f8e3852cfc6)
* fixed wrong relation + lighter style | [commit](https://github.com/undergroundwires/privacy.sexy/commit/8d05b03c9f3c9fc015be6615da8c283809712065) * fixed wrong relation + lighter style | [8d05b03](https://github.com/undergroundwires/privacy.sexy/commit/8d05b03c9f3c9fc015be6615da8c283809712065)
* better URL validation | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aff463dd64fecff92a786fcba88621dff6b1cf73) * better URL validation | [aff463d](https://github.com/undergroundwires/privacy.sexy/commit/aff463dd64fecff92a786fcba88621dff6b1cf73)
* refactoring to new function | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c646c102730481c3f4648eb714dc0a84ce35b13c) * refactoring to new function | [c646c10](https://github.com/undergroundwires/privacy.sexy/commit/c646c102730481c3f4648eb714dc0a84ce35b13c)
* optimized find queries & refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/d38f6cd6a8b33e11df854c7abea05974dc04d4ce) * optimized find queries & refactorings | [d38f6cd](https://github.com/undergroundwires/privacy.sexy/commit/d38f6cd6a8b33e11df854c7abea05974dc04d4ce)
* 🎨 styled no JS error | [commit](https://github.com/undergroundwires/privacy.sexy/commit/c359f1d89c6874b3cc94154b993e33f58bd32268) * 🎨 styled no JS error | [c359f1d](https://github.com/undergroundwires/privacy.sexy/commit/c359f1d89c6874b3cc94154b993e33f58bd32268)
* simplified finding duplicates | [commit](https://github.com/undergroundwires/privacy.sexy/commit/57037aaefcc0e80f0f4719cea89568490a731028) * simplified finding duplicates | [57037aa](https://github.com/undergroundwires/privacy.sexy/commit/57037aaefcc0e80f0f4719cea89568490a731028)
* fixed maintainability badge URL | [commit](https://github.com/undergroundwires/privacy.sexy/commit/aaea47e7d15fe41dea26968db0107a0c53d108f3) * fixed maintainability badge URL | [aaea47e](https://github.com/undergroundwires/privacy.sexy/commit/aaea47e7d15fe41dea26968db0107a0c53d108f3)
* fixed wrong line dumps | [commit](https://github.com/undergroundwires/privacy.sexy/commit/5ccc7c59528885ae7729197df3dfa00f924a2b3f) * fixed wrong line dumps | [5ccc7c5](https://github.com/undergroundwires/privacy.sexy/commit/5ccc7c59528885ae7729197df3dfa00f924a2b3f)
* refactorings in parsing | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2aa3742e30646bf1d1f3779419d161c3fb6c4808) * refactorings in parsing | [2aa3742](https://github.com/undergroundwires/privacy.sexy/commit/2aa3742e30646bf1d1f3779419d161c3fb6c4808)
* using free function | [commit](https://github.com/undergroundwires/privacy.sexy/commit/20020af7c1d8de13948d8761fd4e7f0affb2badb) * using free function | [20020af](https://github.com/undergroundwires/privacy.sexy/commit/20020af7c1d8de13948d8761fd4e7f0affb2badb)
* default selection is now none | [commit](https://github.com/undergroundwires/privacy.sexy/commit/3140cc663b86394d543de90228aa53e6a304d8d9) * default selection is now none | [3140cc6](https://github.com/undergroundwires/privacy.sexy/commit/3140cc663b86394d543de90228aa53e6a304d8d9)
* added hyphen lines for longer names | [commit](https://github.com/undergroundwires/privacy.sexy/commit/cced601d686d550f4225018e5311b7433efbb5ae) * added hyphen lines for longer names | [cced601](https://github.com/undergroundwires/privacy.sexy/commit/cced601d686d550f4225018e5311b7433efbb5ae)
* more descriptive subtitle | [commit](https://github.com/undergroundwires/privacy.sexy/commit/2cf9214b14d9720f747a71b3864ba7a28acf0ff4) * more descriptive subtitle | [2cf9214](https://github.com/undergroundwires/privacy.sexy/commit/2cf9214b14d9720f747a71b3864ba7a28acf0ff4)
* added footer with version | [commit](https://github.com/undergroundwires/privacy.sexy/commit/10a34fae2f1a219ec52db0c74edb39b46ebd8abc) * added footer with version | [10a34fa](https://github.com/undergroundwires/privacy.sexy/commit/10a34fae2f1a219ec52db0c74edb39b46ebd8abc)
* using font variables | [commit](https://github.com/undergroundwires/privacy.sexy/commit/60e6348dc8d53f1e81ebdb2ec0e1962aac1e9842) * using font variables | [60e6348](https://github.com/undergroundwires/privacy.sexy/commit/60e6348dc8d53f1e81ebdb2ec0e1962aac1e9842)
* code-gen refactorings | [commit](https://github.com/undergroundwires/privacy.sexy/commit/246e753ddc9dc8bf630e538663584bf3423cc749) * code-gen refactorings | [246e753](https://github.com/undergroundwires/privacy.sexy/commit/246e753ddc9dc8bf630e538663584bf3423cc749)
* added text when nothing is chosen | [commit](https://github.com/undergroundwires/privacy.sexy/commit/a7da75d4428090423b692ce45423f5bd300d8442) * added text when nothing is chosen | [a7da75d](https://github.com/undergroundwires/privacy.sexy/commit/a7da75d4428090423b692ce45423f5bd300d8442)
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.1.0...0.2.0) [compare](https://github.com/undergroundwires/privacy.sexy/compare/0.1.0...0.2.0)

View File

@@ -30,8 +30,8 @@
- There are two types of components: - There are two types of components:
- **Stateless**, extends `Vue` - **Stateless**, extends `Vue`
- **Stateful**, extends [`StatefulVue`](./src/presentation/StatefulVue.ts) - **Stateful**, extends [`StatefulVue`](./src/presentation/StatefulVue.ts)
- The source of truth for the state lies in application layer (`./src/application/`) and must be updated from the views if they're mutating the state - The source of truth for the state lies in application layer ([`./src/application/`](src/application/)) and must be updated from the views if they're mutating the state
- They mutate or/and reacts to changes in [application state](src/application/State/ApplicationState.ts). - They mutate or/and react to state changes in [ApplicationContext](src/application/Context/ApplicationContext.ts).
- You can react by getting the state and listening to it and update the view accordingly in [`mounted()`](https://vuejs.org/v2/api/#mounted) method. - You can react by getting the state and listening to it and update the view accordingly in [`mounted()`](https://vuejs.org/v2/api/#mounted) method.
## License ## License

View File

@@ -1,6 +1,6 @@
# privacy.sexy # privacy.sexy
> Enforce privacy & security best-practices on Windows, because privacy is sexy 🍑🍆 > Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](./CONTRIBUTING.md) [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](./CONTRIBUTING.md)
[![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript)
@@ -15,7 +15,7 @@
## Get started ## Get started
- Online version: [https://privacy.sexy](https://privacy.sexy) - Online version: [https://privacy.sexy](https://privacy.sexy)
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.8.0/privacy.sexy-Setup-0.8.0.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.8.0/privacy.sexy-0.8.0.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.8.0/privacy.sexy-0.8.0.dmg) - or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-Setup-0.9.0.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-0.9.0.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-0.9.0.dmg)
- 💡 Come back regularly to apply latest version for stronger privacy and security. - 💡 Come back regularly to apply latest version for stronger privacy and security.
[![privacy.sexy application](img/screenshot.png)](https://privacy.sexy) [![privacy.sexy application](img/screenshot.png)](https://privacy.sexy)
@@ -32,9 +32,11 @@
## Extend scripts ## Extend scripts
- Fork it & add more scripts in [application.yaml](src/application/application.yaml) and send a pull request 👌 1. Fork the repository
- 📖 If you're unsure about the syntax you can refer to the [application file | documentation](docs/application-file.md). 2. Add more scripts in respective script collection in [collections](src/application/collections/) folder.
- 🙏 For any new script, please add `revertCode` and `docs` values if possible. - 📖 If you're unsure about the syntax you can refer to the [collection files | documentation](docs/collection-files.md).
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
3. Send a pull request 👌
## Commands ## Commands
@@ -49,8 +51,8 @@
- Development: `npm run serve` to compile & hot-reload for development. - Development: `npm run serve` to compile & hot-reload for development.
- Production: `npm run build` to prepare files for distribution. - Production: `npm run build` to prepare files for distribution.
- Or run using Docker: - Or run using Docker:
1. Build: `docker build -t undergroundwires/privacy.sexy:0.8.0 .` 1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.0 .`
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.8.0 undergroundwires/privacy.sexy:0.8.0` 2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.0 undergroundwires/privacy.sexy:0.9.0`
## Architecture ## Architecture
@@ -65,9 +67,13 @@
- Desktop application is created using [Electron](https://www.electronjs.org/). - Desktop application is created using [Electron](https://www.electronjs.org/).
- Event driven as in components simply listens to events from the state and act accordingly. - Event driven as in components simply listens to events from the state and act accordingly.
- **Application Layer** - **Application Layer**
- Keeps the application state - Keeps the application state using [state pattern](https://en.wikipedia.org/wiki/State_pattern)
- The [state](src/application/State/ApplicationState.ts) is a mutable singleton & event producer. - [ApplicationContext](src/application/Context/ApplicationContext.ts)
- The application is defined & controlled in a [single YAML file](src/application/application.yaml) (see [Data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming)) - Holds the [CategoryCollectionState](src/application/Context/State/CategoryCollectionState.ts)] for each OS
- Same instance is shared throughout the application
- The scripts are defined and controlled in [yaml files](src/application/collections/) per OS
- Uses [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming)
- 📖 See [extend scripts](#extend-scripts) to read about how to extend them.
![DDD + vue.js](img/architecture/app-ddd.png) ![DDD + vue.js](img/architecture/app-ddd.png)

5
babel.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

View File

@@ -1,25 +1,32 @@
# Application file # Collection files
- privacy.sexy is a data-driven application where it reads the necessary OS-specific logic from [`application.yaml`](./../src/application/application.yaml) - privacy.sexy is a data-driven application where it reads the necessary OS-specific logic from yaml files in [`application/collections`](./../src/application/collections/)
- 💡 Best practices - 💡 Best practices
- If you repeat yourself, try to utilize [YAML-defined functions](#function) - If you repeat yourself, try to utilize [YAML-defined functions](#Function)
- Always try to add documentation and a way to revert a tweak in [scripts](#script) - Always try to add documentation and a way to revert a tweak in [scripts](#Script)
- 📖 Types in code: [`application.d.ts`](./../src/application/application.yaml.d.ts) - 📖 Types in code: [`collection.yaml.d.ts`](./../src/application/collections/collection.yaml.d.ts)
## Objects ## Objects
### `Application` ### `Collection`
- Application file simply defines different categories and their scripts in a tree structure. - A collection simply defines:
- Application file also allows defining common [function](#function)s to be used throughout the application if you'd like different scripts to share same code. - different categories and their scripts in a tree structure
- OS specific details
- Also allows defining common [function](#Function)s to be used throughout the collection if you'd like different scripts to share same code.
#### `Application` syntax #### `Collection` syntax
- `os:` *`string`* (**required**)
- Operating system that the [Collection](#collection) is written for.
- 📖 See [OperatingSystem.ts](./../src/domain/OperatingSystem.ts) enumeration for allowed values.
- `actions: [` ***[`Category`](#Category)*** `, ... ]` **(required)** - `actions: [` ***[`Category`](#Category)*** `, ... ]` **(required)**
- Each [category](#category) is rendered as different cards in card presentation. - Each [category](#category) is rendered as different cards in card presentation.
- ❗ Application must consist of at least one category. - ❗ A [Collection](#collection) must consist of at least one category.
- `functions: [` ***[`Function`](#Function)*** `, ... ]` - `functions: [` ***[`Function`](#Function)*** `, ... ]`
- Functions are optionally defined to re-use the same code throughout different scripts. - Functions are optionally defined to re-use the same code throughout different scripts.
- `scripting:` ***[`ScriptingDefinition`](#ScriptingDefinition)*** **(required)**
- Defines the scripting language that the code of other action uses.
### `Category` ### `Category`
@@ -30,8 +37,8 @@
- `category:` *`string`* (**required**) - `category:` *`string`* (**required**)
- Name of the category - Name of the category
- ❗ Must be unique throughout the application - ❗ Must be unique throughout the [Collection](#collection)
- `children: [` ***[`Category`](#category)*** `|` [***`Script`***](#Script) `, ... ]` (**required**) - `children: [` ***[`Category`](#Category)*** `|` [***`Script`***](#Script) `, ... ]` (**required**)
- ❗ Category must consist of at least one subcategory or script. - ❗ Category must consist of at least one subcategory or script.
- Children can be combination of scripts and subcategories. - Children can be combination of scripts and subcategories.
@@ -47,7 +54,7 @@
- `name`: *`string`* (**required**) - `name`: *`string`* (**required**)
- Name of the script - Name of the script
- ❗ Must be unique throughout the application - ❗ Must be unique throughout the [Collection](#collection)
- E.g. `Disable targeted ads` - E.g. `Disable targeted ads`
- `code`: *`string`* (may be **required**) - `code`: *`string`* (may be **required**)
- Batch file commands that will be executed - Batch file commands that will be executed
@@ -79,7 +86,7 @@
- `function`: *`string`* (**required**) - `function`: *`string`* (**required**)
- Name of the function to call. - Name of the function to call.
- ❗ Function with same name must defined in `functions` property of [Application](#application) - ❗ Function with same name must defined in `functions` property of [Collection](#collection)
- `parameters`: `[ parameterName:` *`parameterValue`*`, ... ]` - `parameters`: `[ parameterName:` *`parameterValue`*`, ... ]`
- Defines key value dictionary for each parameter and its value - Defines key value dictionary for each parameter and its value
- E.g. - E.g.
@@ -127,7 +134,7 @@ It would print "Hello world" if it's called in a [script](#script) as following:
- ❗ Function names must be unique - ❗ Function names must be unique
- `parameters`: `[` *`string`* `, ... ]` - `parameters`: `[` *`string`* `, ... ]`
- Name of the parameters that the function has. - Name of the parameters that the function has.
- Parameter values are provided by a [Script](#script) through a [FunctionCall](#functioncall) - Parameter values are provided by a [Script](#script) through a [FunctionCall](#FunctionCall)
- Parameter names must be defined to be used in expressions such as [parameter substitution](#parameter-substitution) - Parameter names must be defined to be used in expressions such as [parameter substitution](#parameter-substitution)
- ❗ Parameter names must be unique - ❗ Parameter names must be unique
`code`: *`string`* (**required**) `code`: *`string`* (**required**)
@@ -137,3 +144,18 @@ It would print "Hello world" if it's called in a [script](#script) as following:
- Code that'll undo the change done by `code` property. - Code that'll undo the change done by `code` property.
- E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1` - E.g. let's say `code` sets an environment variable as `setx POWERSHELL_TELEMETRY_OPTOUT 1`
- then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0` - then `revertCode` should be doing `setx POWERSHELL_TELEMETRY_OPTOUT 0`
### `ScriptingDefinition`
- Defines global properties for scripting that's used throughout its parent [Collection](#collection).
#### `ScriptingDefinition` syntax
- `language:` *`string`* (**required**)
- 📖 See [ScriptingLanguage.ts](./../src/domain/ScriptingLanguage.ts) enumeration for allowed values.
- `startCode:` *`string`* (**required**)
- Code that'll be inserted on top of user created script.
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
- `endCode:` *`string`* (**required**)
- Code that'll be inserted at the end of user created script.
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

4173
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,9 @@
{ {
"name": "privacy.sexy", "name": "privacy.sexy",
"version": "0.8.0", "version": "0.9.0",
"author": "undergroundwires",
"description": "Enforce privacy & security best-practices on Windows, because privacy is sexy 🍑🍆",
"homepage": "https://privacy.sexy",
"private": true, "private": true,
"repository": { "description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
"type": "git", "author": "undergroundwires",
"url": "https://github.com/undergroundwires/privacy.sexy.git"
},
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
@@ -30,42 +25,49 @@
"@fortawesome/free-brands-svg-icons": "^5.15.1", "@fortawesome/free-brands-svg-icons": "^5.15.1",
"@fortawesome/free-regular-svg-icons": "^5.15.1", "@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.15.1", "@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/vue-fontawesome": "^2.0.0", "@fortawesome/vue-fontawesome": "^2.0.2",
"ace-builds": "^1.4.12", "ace-builds": "^1.4.12",
"file-saver": "^2.0.2", "core-js": "^3.6.5",
"inversify": "^5.0.1", "file-saver": "^2.0.5",
"inversify": "^5.0.5",
"liquor-tree": "^0.2.70", "liquor-tree": "^0.2.70",
"v-tooltip": "2.0.2", "v-tooltip": "2.0.2",
"vue": "^2.6.12", "vue": "^2.6.12",
"vue-class-component": "^7.2.6", "vue-class-component": "^7.2.6",
"vue-js-modal": "^2.0.0-rc.6", "vue-js-modal": "^2.0.0-rc.6",
"vue-property-decorator": "^9.0.2" "vue-property-decorator": "^9.1.2"
}, },
"devDependencies": { "devDependencies": {
"@types/ace": "0.0.44", "@types/ace": "0.0.44",
"@types/chai": "^4.2.14", "@types/chai": "^4.2.14",
"@types/file-saver": "^2.0.1", "@types/file-saver": "^2.0.1",
"@types/mocha": "^8.0.3", "@types/mocha": "^8.2.0",
"@vue/cli-plugin-typescript": "^4.5.7", "@vue/cli-plugin-babel": "^4.5.10",
"@vue/cli-plugin-unit-mocha": "^4.5.7", "@vue/cli-plugin-typescript": "^4.5.9",
"@vue/cli-service": "^4.5.7", "@vue/cli-plugin-unit-mocha": "^4.5.9",
"@vue/test-utils": "1.1.0", "@vue/cli-service": "^4.5.9",
"@vue/test-utils": "1.1.2",
"chai": "^4.2.0", "chai": "^4.2.0",
"electron": "^10.1.3", "electron": "^11.1.0",
"electron-devtools-installer": "^3.1.1", "electron-devtools-installer": "^3.1.1",
"electron-log": "^4.2.4", "electron-log": "^4.3.1",
"electron-updater": "^4.3.5", "electron-updater": "^4.3.5",
"js-yaml-loader": "^1.2.2", "js-yaml-loader": "^1.2.2",
"markdownlint-cli": "^0.24.0", "markdownlint-cli": "^0.26.0",
"remark-cli": "^9.0.0", "remark-cli": "^9.0.0",
"remark-lint-no-dead-urls": "^1.1.0", "remark-lint-no-dead-urls": "^1.1.0",
"remark-preset-lint-consistent": "^4.0.0", "remark-preset-lint-consistent": "^4.0.0",
"remark-validate-links": "^10.0.2", "remark-validate-links": "^10.0.2",
"sass": "^1.27.0", "sass": "^1.30.0",
"sass-loader": "^10.0.3", "sass-loader": "^10.1.0",
"typescript": "^4.0.3", "typescript": "^4.1.3",
"vue-cli-plugin-electron-builder": "^2.0.0-rc.4", "vue-cli-plugin-electron-builder": "^2.0.0-rc.5",
"vue-template-compiler": "^2.6.12", "vue-template-compiler": "^2.6.12",
"yaml-lint": "^1.2.4" "yaml-lint": "^1.2.4"
},
"homepage": "https://privacy.sexy",
"repository": {
"type": "git",
"url": "https://github.com/undergroundwires/privacy.sexy.git"
} }
} }

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Privacy is sexy 🍑🍆 - Enforce privacy & security on Windows</title> <title>Privacy is sexy 🍑🍆 - Enforce privacy & security on Windows and macOS</title>
<meta name="robots" content="index,follow" /> <meta name="robots" content="index,follow" />
<meta name="description" content="Web tool to generate scripts for enforcing privacy & security best-practices such as stopping data collection of Windows and different softwares on it."/> <meta name="description" content="Web tool to generate scripts for enforcing privacy & security best-practices such as stopping data collection of Windows and different softwares on it."/>
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">

View File

@@ -16,7 +16,7 @@ import { Component, Vue } from 'vue-property-decorator';
import TheHeader from '@/presentation/TheHeader.vue'; import TheHeader from '@/presentation/TheHeader.vue';
import TheFooter from '@/presentation/TheFooter/TheFooter.vue'; import TheFooter from '@/presentation/TheFooter/TheFooter.vue';
import TheCodeArea from '@/presentation/TheCodeArea.vue'; import TheCodeArea from '@/presentation/TheCodeArea.vue';
import TheCodeButtons from '@/presentation/TheCodeButtons.vue'; import TheCodeButtons from '@/presentation/CodeButtons/TheCodeButtons.vue';
import TheSearchBar from '@/presentation/TheSearchBar.vue'; import TheSearchBar from '@/presentation/TheSearchBar.vue';
import TheScripts from '@/presentation/Scripts/TheScripts.vue'; import TheScripts from '@/presentation/Scripts/TheScripts.vue';

View File

@@ -0,0 +1,43 @@
// Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611
type EnumType = number | string;
type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
export interface IEnumParser<TEnum> {
parseEnum(value: string, propertyName: string): TEnum;
}
export function createEnumParser<T extends EnumType, TEnumValue extends EnumType>(
enumVariable: EnumVariable<T, TEnumValue>): IEnumParser<TEnumValue> {
return {
parseEnum: (value, propertyName) => parseEnumValue(value, propertyName, enumVariable),
};
}
function parseEnumValue<T extends EnumType, TEnumValue extends EnumType>(
value: string,
enumName: string,
enumVariable: EnumVariable<T, TEnumValue>): TEnumValue {
if (!value) {
throw new Error(`undefined ${enumName}`);
}
if (typeof value !== 'string') {
throw new Error(`unexpected type of ${enumName}: "${typeof value}"`);
}
const casedValue = getEnumNames(enumVariable)
.find((enumValue) => enumValue.toLowerCase() === value.toLowerCase());
if (!casedValue) {
throw new Error(`unknown ${enumName}: "${value}"`);
}
return enumVariable[casedValue as keyof typeof enumVariable];
}
export function getEnumNames<T extends EnumType, TEnumValue extends EnumType>(
enumVariable: EnumVariable<T, TEnumValue>): string[] {
return Object
.values(enumVariable)
.filter((enumMember) => typeof enumMember === 'string') as string[];
}
export function getEnumValues<T extends EnumType, TEnumValue extends EnumType>(
enumVariable: EnumVariable<T, TEnumValue>): TEnumValue[] {
return getEnumNames(enumVariable)
.map((level) => enumVariable[level]) as TEnumValue[];
}

View File

@@ -0,0 +1,71 @@
import { IApplicationContext, IApplicationContextChangedEvent } from './IApplicationContext';
import { ICategoryCollectionState } from './State/ICategoryCollectionState';
import { CategoryCollectionState } from './State/CategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { Signal } from '@/infrastructure/Events/Signal';
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
export class ApplicationContext implements IApplicationContext {
public readonly contextChanged = new Signal<IApplicationContextChangedEvent>();
public collection: ICategoryCollection;
public currentOs: OperatingSystem;
public get state(): ICategoryCollectionState {
return this.states[this.collection.os];
}
private readonly states: StateMachine;
public constructor(
public readonly app: IApplication,
initialContext: OperatingSystem) {
validateApp(app);
validateOs(initialContext);
this.states = initializeStates(app);
this.changeContext(initialContext);
}
public changeContext(os: OperatingSystem): void {
if (this.currentOs === os) {
return;
}
this.collection = this.app.getCollection(os);
if (!this.collection) {
throw new Error(`os "${OperatingSystem[os]}" is not defined in application`);
}
const event: IApplicationContextChangedEvent = {
newState: this.states[os],
oldState: this.states[this.currentOs],
};
this.contextChanged.notify(event);
this.currentOs = os;
}
}
function validateApp(app: IApplication) {
if (!app) {
throw new Error('undefined app');
}
}
function validateOs(os: OperatingSystem) {
if (os === undefined) {
throw new Error('undefined os');
}
if (os === OperatingSystem.Unknown) {
throw new Error('unknown os');
}
if (!(os in OperatingSystem)) {
throw new Error(`os "${os}" is out of range`);
}
}
function initializeStates(app: IApplication): StateMachine {
const machine = new Map<OperatingSystem, ICategoryCollectionState>();
for (const collection of app.collections) {
machine[collection.os] = new CategoryCollectionState(collection);
}
return machine;
}

View File

@@ -0,0 +1,31 @@
import { ApplicationContext } from './ApplicationContext';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { Environment } from '../Environment/Environment';
import { IApplication } from '@/domain/IApplication';
import { IEnvironment } from '../Environment/IEnvironment';
import { parseApplication } from '../Parser/ApplicationParser';
export type ApplicationParserType = () => IApplication;
const ApplicationParser: ApplicationParserType = parseApplication;
export function buildContext(
parser = ApplicationParser,
environment = Environment.CurrentEnvironment): IApplicationContext {
const app = parser();
const os = getInitialOs(app, environment);
return new ApplicationContext(app, os);
}
function getInitialOs(app: IApplication, environment: IEnvironment): OperatingSystem {
const currentOs = environment.os;
const supportedOsList = app.getSupportedOsList();
if (supportedOsList.includes(currentOs)) {
return currentOs;
}
supportedOsList.sort((os1, os2) => {
const getPriority = (os: OperatingSystem) => app.getCollection(os).totalScripts;
return getPriority(os2) - getPriority(os1);
});
return supportedOsList[0];
}

View File

@@ -0,0 +1,16 @@
import { ICategoryCollectionState } from './State/ICategoryCollectionState';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { ISignal } from '@/infrastructure/Events/ISignal';
import { IApplication } from '@/domain/IApplication';
export interface IApplicationContext {
readonly app: IApplication;
readonly state: ICategoryCollectionState;
readonly contextChanged: ISignal<IApplicationContextChangedEvent>;
changeContext(os: OperatingSystem): void;
}
export interface IApplicationContextChangedEvent {
readonly newState: ICategoryCollectionState;
readonly oldState: ICategoryCollectionState;
}

View File

@@ -0,0 +1,23 @@
import { UserFilter } from './Filter/UserFilter';
import { IUserFilter } from './Filter/IUserFilter';
import { ApplicationCode } from './Code/ApplicationCode';
import { UserSelection } from './Selection/UserSelection';
import { IUserSelection } from './Selection/IUserSelection';
import { ICategoryCollectionState } from './ICategoryCollectionState';
import { IApplicationCode } from './Code/IApplicationCode';
import { ICategoryCollection } from '../../../domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
export class CategoryCollectionState implements ICategoryCollectionState {
public readonly os: OperatingSystem;
public readonly code: IApplicationCode;
public readonly selection: IUserSelection;
public readonly filter: IUserFilter;
public constructor(readonly collection: ICategoryCollection) {
this.selection = new UserSelection(collection, []);
this.code = new ApplicationCode(this.selection, collection.scripting);
this.filter = new UserFilter(collection);
this.os = collection.os;
}
}

View File

@@ -1,12 +1,13 @@
import { CodeChangedEvent } from './Event/CodeChangedEvent'; import { CodeChangedEvent } from './Event/CodeChangedEvent';
import { CodePosition } from './Position/CodePosition'; import { CodePosition } from './Position/CodePosition';
import { ICodeChangedEvent } from './Event/ICodeChangedEvent'; import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
import { SelectedScript } from '@/application/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IUserSelection } from '@/application/State/Selection/IUserSelection'; import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
import { UserScriptGenerator } from './Generation/UserScriptGenerator'; import { UserScriptGenerator } from './Generation/UserScriptGenerator';
import { Signal } from '@/infrastructure/Events/Signal'; import { Signal } from '@/infrastructure/Events/Signal';
import { IApplicationCode } from './IApplicationCode'; import { IApplicationCode } from './IApplicationCode';
import { IUserScriptGenerator } from './Generation/IUserScriptGenerator'; import { IUserScriptGenerator } from './Generation/IUserScriptGenerator';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
export class ApplicationCode implements IApplicationCode { export class ApplicationCode implements IApplicationCode {
public readonly changed = new Signal<ICodeChangedEvent>(); public readonly changed = new Signal<ICodeChangedEvent>();
@@ -16,10 +17,10 @@ export class ApplicationCode implements IApplicationCode {
constructor( constructor(
userSelection: IUserSelection, userSelection: IUserSelection,
private readonly version: string, private readonly scriptingDefinition: IScriptingDefinition,
private readonly generator: IUserScriptGenerator = new UserScriptGenerator()) { private readonly generator: IUserScriptGenerator = new UserScriptGenerator()) {
if (!userSelection) { throw new Error('userSelection is null or undefined'); } if (!userSelection) { throw new Error('userSelection is null or undefined'); }
if (!version) { throw new Error('version is null or undefined'); } if (!scriptingDefinition) { throw new Error('scriptingDefinition is null or undefined'); }
if (!generator) { throw new Error('generator is null or undefined'); } if (!generator) { throw new Error('generator is null or undefined'); }
this.setCode(userSelection.selectedScripts); this.setCode(userSelection.selectedScripts);
userSelection.changed.on((scripts) => { userSelection.changed.on((scripts) => {
@@ -29,7 +30,7 @@ export class ApplicationCode implements IApplicationCode {
private setCode(scripts: ReadonlyArray<SelectedScript>): void { private setCode(scripts: ReadonlyArray<SelectedScript>): void {
const oldScripts = Array.from(this.scriptPositions.keys()); const oldScripts = Array.from(this.scriptPositions.keys());
const code = this.generator.buildCode(scripts, this.version); const code = this.generator.buildCode(scripts, this.scriptingDefinition);
this.current = code.code; this.current = code.code;
this.scriptPositions = code.scriptPositions; this.scriptPositions = code.scriptPositions;
const event = new CodeChangedEvent(code.code, oldScripts, code.scriptPositions); const event = new CodeChangedEvent(code.code, oldScripts, code.scriptPositions);

View File

@@ -1,7 +1,7 @@
import { ICodeChangedEvent } from './ICodeChangedEvent'; import { ICodeChangedEvent } from './ICodeChangedEvent';
import { SelectedScript } from '../../Selection/SelectedScript'; import { SelectedScript } from '../../Selection/SelectedScript';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition'; import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
export class CodeChangedEvent implements ICodeChangedEvent { export class CodeChangedEvent implements ICodeChangedEvent {
public readonly code: string; public readonly code: string;

View File

@@ -1,5 +1,5 @@
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition'; import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
export interface ICodeChangedEvent { export interface ICodeChangedEvent {
readonly code: string; readonly code: string;

View File

@@ -1,7 +1,9 @@
import { ICodeBuilder } from './ICodeBuilder';
const NewLine = '\n'; const NewLine = '\n';
const TotalFunctionSeparatorChars = 58; const TotalFunctionSeparatorChars = 58;
export class CodeBuilder { export abstract class CodeBuilder implements ICodeBuilder {
private readonly lines = new Array<string>(); private readonly lines = new Array<string>();
// Returns current line starting from 0 (no lines), or 1 (have single line) // Returns current line starting from 0 (no lines), or 1 (have single line)
@@ -27,7 +29,7 @@ export class CodeBuilder {
} }
public appendCommentLine(commentLine?: string): CodeBuilder { public appendCommentLine(commentLine?: string): CodeBuilder {
this.lines.push(`:: ${commentLine}`); this.lines.push(`${this.getCommentDelimiter()} ${commentLine}`);
return this; return this;
} }
@@ -35,9 +37,8 @@ export class CodeBuilder {
if (!name) { throw new Error('name cannot be empty or null'); } if (!name) { throw new Error('name cannot be empty or null'); }
if (!code) { throw new Error('code cannot be empty or null'); } if (!code) { throw new Error('code cannot be empty or null'); }
return this return this
.appendLine()
.appendCommentLineWithHyphensAround(name) .appendCommentLineWithHyphensAround(name)
.appendLine(`echo --- ${name}`) .appendLine(this.writeStandardOut(`--- ${name}`))
.appendLine(code) .appendLine(code)
.appendTrailingHyphensCommentLine(); .appendTrailingHyphensCommentLine();
} }
@@ -54,10 +55,13 @@ export class CodeBuilder {
return this return this
.appendTrailingHyphensCommentLine() .appendTrailingHyphensCommentLine()
.appendCommentLine(firstHyphens + sectionName + secondHyphens) .appendCommentLine(firstHyphens + sectionName + secondHyphens)
.appendTrailingHyphensCommentLine(); .appendTrailingHyphensCommentLine(TotalFunctionSeparatorChars);
} }
public toString(): string { public toString(): string {
return this.lines.join(NewLine); return this.lines.join(NewLine);
} }
protected abstract getCommentDelimiter(): string;
protected abstract writeStandardOut(text: string): string;
} }

View File

@@ -0,0 +1,15 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
import { BatchBuilder } from './Languages/BatchBuilder';
import { ShellBuilder } from './Languages/ShellBuilder';
export class CodeBuilderFactory implements ICodeBuilderFactory {
public create(language: ScriptingLanguage): ICodeBuilder {
switch (language) {
case ScriptingLanguage.shellscript: return new ShellBuilder();
case ScriptingLanguage.batchfile: return new BatchBuilder();
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
}
}

View File

@@ -0,0 +1,9 @@
export interface ICodeBuilder {
currentLine: number;
appendLine(code?: string): ICodeBuilder;
appendTrailingHyphensCommentLine(totalRepeatHyphens: number): ICodeBuilder;
appendCommentLine(commentLine?: string): ICodeBuilder;
appendCommentLineWithHyphensAround(sectionName: string, totalRepeatHyphens: number): ICodeBuilder;
appendFunction(name: string, code: string): ICodeBuilder;
toString(): string;
}

View File

@@ -0,0 +1,6 @@
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ICodeBuilder } from './ICodeBuilder';
export interface ICodeBuilderFactory {
create(language: ScriptingLanguage): ICodeBuilder;
}

View File

@@ -0,0 +1,7 @@
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
export interface IUserScript {
code: string;
scriptPositions: Map<SelectedScript, ICodePosition>;
}

View File

@@ -0,0 +1,9 @@
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IUserScript } from './IUserScript';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
export interface IUserScriptGenerator {
buildCode(
selectedScripts: ReadonlyArray<SelectedScript>,
scriptingDefinition: IScriptingDefinition): IUserScript;
}

View File

@@ -0,0 +1,10 @@
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
export class BatchBuilder extends CodeBuilder {
protected getCommentDelimiter(): string {
return '::';
}
protected writeStandardOut(text: string): string {
return `echo ${text}`;
}
}

View File

@@ -0,0 +1,10 @@
import { CodeBuilder } from '@/application/Context/State/Code/Generation/CodeBuilder';
export class ShellBuilder extends CodeBuilder {
protected getCommentDelimiter(): string {
return '#';
}
protected writeStandardOut(text: string): string {
return `echo '${text}'`;
}
}

View File

@@ -0,0 +1,71 @@
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IUserScriptGenerator } from './IUserScriptGenerator';
import { ICodePosition } from '@/application/Context/State/Code/Position/ICodePosition';
import { CodePosition } from '../Position/CodePosition';
import { IUserScript } from './IUserScript';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ICodeBuilder } from './ICodeBuilder';
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
import { CodeBuilderFactory } from './CodeBuilderFactory';
export class UserScriptGenerator implements IUserScriptGenerator {
constructor(private readonly codeBuilderFactory: ICodeBuilderFactory = new CodeBuilderFactory()) {
}
public buildCode(
selectedScripts: ReadonlyArray<SelectedScript>,
scriptingDefinition: IScriptingDefinition): IUserScript {
if (!selectedScripts) { throw new Error('undefined scripts'); }
if (!scriptingDefinition) { throw new Error('undefined definition'); }
let scriptPositions = new Map<SelectedScript, ICodePosition>();
if (!selectedScripts.length) {
return { code: '', scriptPositions };
}
let builder = this.codeBuilderFactory.create(scriptingDefinition.language);
builder = initializeCode(scriptingDefinition.startCode, builder);
for (const selection of selectedScripts) {
scriptPositions = appendSelection(selection, scriptPositions, builder);
}
const code = finalizeCode(builder, scriptingDefinition.endCode);
return { code, scriptPositions };
}
}
function initializeCode(startCode: string, builder: ICodeBuilder): ICodeBuilder {
if (!startCode) {
return builder;
}
return builder
.appendLine(startCode)
.appendLine();
}
function finalizeCode(builder: ICodeBuilder, endCode: string): string {
if (!endCode) {
return builder.toString();
}
return builder.appendLine()
.appendLine(endCode)
.toString();
}
function appendSelection(
selection: SelectedScript,
scriptPositions: Map<SelectedScript, ICodePosition>,
builder: ICodeBuilder): Map<SelectedScript, ICodePosition> {
const startPosition = builder.currentLine + 1; // Because first line will be empty to separate scripts
builder = appendCode(selection, builder);
const endPosition = builder.currentLine - 1;
builder.appendLine();
const position = new CodePosition(startPosition, endPosition);
scriptPositions.set(selection, position);
return scriptPositions;
}
function appendCode(selection: SelectedScript, builder: ICodeBuilder): ICodeBuilder {
const name = selection.revert ? `${selection.script.name} (revert)` : selection.script.name;
const scriptCode = selection.revert ? selection.script.code.revert : selection.script.code.execute;
return builder
.appendLine()
.appendFunction(name, scriptCode);
}

View File

@@ -1,6 +1,6 @@
import { ICodePosition } from './ICodePosition'; import { ICodePosition } from './ICodePosition';
export class CodePosition implements ICodePosition {
export class CodePosition implements ICodePosition {
public get totalLines(): number { public get totalLines(): number {
return this.endLine - this.startLine; return this.endLine - this.startLine;
} }

View File

@@ -1,5 +1,5 @@
import { ISignal } from '@/infrastructure/Events/ISignal';
import { IFilterResult } from './IFilterResult'; import { IFilterResult } from './IFilterResult';
import { ISignal } from '@/infrastructure/Events/Signal';
export interface IUserFilter { export interface IUserFilter {
readonly currentFilter: IFilterResult | undefined; readonly currentFilter: IFilterResult | undefined;

View File

@@ -1,16 +1,16 @@
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { FilterResult } from './FilterResult'; import { FilterResult } from './FilterResult';
import { IFilterResult } from './IFilterResult'; import { IFilterResult } from './IFilterResult';
import { IApplication } from '@/domain/IApplication';
import { IUserFilter } from './IUserFilter'; import { IUserFilter } from './IUserFilter';
import { Signal } from '@/infrastructure/Events/Signal'; import { Signal } from '@/infrastructure/Events/Signal';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
export class UserFilter implements IUserFilter { export class UserFilter implements IUserFilter {
public readonly filtered = new Signal<IFilterResult>(); public readonly filtered = new Signal<IFilterResult>();
public readonly filterRemoved = new Signal<void>(); public readonly filterRemoved = new Signal<void>();
public currentFilter: IFilterResult | undefined; public currentFilter: IFilterResult | undefined;
constructor(private application: IApplication) { constructor(private collection: ICategoryCollection) {
} }
@@ -19,9 +19,9 @@ export class UserFilter implements IUserFilter {
throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter'); throw new Error('Filter must be defined and not empty. Use removeFilter() to remove the filter');
} }
const filterLowercase = filter.toLocaleLowerCase(); const filterLowercase = filter.toLocaleLowerCase();
const filteredScripts = this.application.getAllScripts().filter( const filteredScripts = this.collection.getAllScripts().filter(
(script) => isScriptAMatch(script, filterLowercase)); (script) => isScriptAMatch(script, filterLowercase));
const filteredCategories = this.application.getAllCategories().filter( const filteredCategories = this.collection.getAllCategories().filter(
(category) => category.name.toLowerCase().includes(filterLowercase)); (category) => category.name.toLowerCase().includes(filterLowercase));
const matches = new FilterResult( const matches = new FilterResult(
filteredScripts, filteredScripts,

View File

@@ -0,0 +1,13 @@
import { IUserFilter } from './Filter/IUserFilter';
import { IUserSelection } from './Selection/IUserSelection';
import { IApplicationCode } from './Code/IApplicationCode';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { OperatingSystem } from '@/domain/OperatingSystem';
export interface ICategoryCollectionState {
readonly code: IApplicationCode;
readonly filter: IUserFilter;
readonly selection: IUserSelection;
readonly collection: ICategoryCollection;
readonly os: OperatingSystem;
}

View File

@@ -1,7 +1,7 @@
import { SelectedScript } from './SelectedScript'; import { SelectedScript } from './SelectedScript';
import { ISignal } from '@/infrastructure/Events/Signal';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { ISignal } from '@/infrastructure/Events/ISignal';
export interface IUserSelection { export interface IUserSelection {
readonly changed: ISignal<ReadonlyArray<SelectedScript>>; readonly changed: ISignal<ReadonlyArray<SelectedScript>>;

View File

@@ -1,17 +1,18 @@
import { SelectedScript } from './SelectedScript'; import { SelectedScript } from './SelectedScript';
import { IApplication, ICategory } from '@/domain/IApplication';
import { IUserSelection } from './IUserSelection'; import { IUserSelection } from './IUserSelection';
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository'; import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { Signal } from '@/infrastructure/Events/Signal'; import { Signal } from '@/infrastructure/Events/Signal';
import { IRepository } from '@/infrastructure/Repository/IRepository'; import { IRepository } from '@/infrastructure/Repository/IRepository';
import { ICategory } from '@/domain/ICategory';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
export class UserSelection implements IUserSelection { export class UserSelection implements IUserSelection {
public readonly changed = new Signal<ReadonlyArray<SelectedScript>>(); public readonly changed = new Signal<ReadonlyArray<SelectedScript>>();
private readonly scripts: IRepository<string, SelectedScript>; private readonly scripts: IRepository<string, SelectedScript>;
constructor( constructor(
private readonly app: IApplication, private readonly collection: ICategoryCollection,
selectedScripts: ReadonlyArray<SelectedScript>) { selectedScripts: ReadonlyArray<SelectedScript>) {
this.scripts = new InMemoryRepository<string, SelectedScript>(); this.scripts = new InMemoryRepository<string, SelectedScript>();
if (selectedScripts && selectedScripts.length > 0) { if (selectedScripts && selectedScripts.length > 0) {
@@ -40,7 +41,7 @@ export class UserSelection implements IUserSelection {
} }
public removeAllInCategory(categoryId: number): void { public removeAllInCategory(categoryId: number): void {
const category = this.app.findCategory(categoryId); const category = this.collection.findCategory(categoryId);
const scriptsToRemove = category.getAllScriptsRecursively() const scriptsToRemove = category.getAllScriptsRecursively()
.filter((script) => this.scripts.exists(script.id)); .filter((script) => this.scripts.exists(script.id));
if (!scriptsToRemove.length) { if (!scriptsToRemove.length) {
@@ -53,7 +54,7 @@ export class UserSelection implements IUserSelection {
} }
public addOrUpdateAllInCategory(categoryId: number, revert: boolean = false): void { public addOrUpdateAllInCategory(categoryId: number, revert: boolean = false): void {
const category = this.app.findCategory(categoryId); const category = this.collection.findCategory(categoryId);
const scriptsToAddOrUpdate = category.getAllScriptsRecursively() const scriptsToAddOrUpdate = category.getAllScriptsRecursively()
.filter((script) => .filter((script) =>
!this.scripts.exists(script.id) !this.scripts.exists(script.id)
@@ -70,7 +71,7 @@ export class UserSelection implements IUserSelection {
} }
public addSelectedScript(scriptId: string, revert: boolean): void { public addSelectedScript(scriptId: string, revert: boolean): void {
const script = this.app.findScript(scriptId); const script = this.collection.findScript(scriptId);
if (!script) { if (!script) {
throw new Error(`Cannot add (id: ${scriptId}) as it is unknown`); throw new Error(`Cannot add (id: ${scriptId}) as it is unknown`);
} }
@@ -80,7 +81,7 @@ export class UserSelection implements IUserSelection {
} }
public addOrUpdateSelectedScript(scriptId: string, revert: boolean): void { public addOrUpdateSelectedScript(scriptId: string, revert: boolean): void {
const script = this.app.findScript(scriptId); const script = this.collection.findScript(scriptId);
const selectedScript = new SelectedScript(script, revert); const selectedScript = new SelectedScript(script, revert);
this.scripts.addOrUpdateItem(selectedScript); this.scripts.addOrUpdateItem(selectedScript);
this.changed.notify(this.scripts.getItems()); this.changed.notify(this.scripts.getItems());
@@ -105,7 +106,7 @@ export class UserSelection implements IUserSelection {
} }
public selectAll(): void { public selectAll(): void {
for (const script of this.app.getAllScripts()) { for (const script of this.collection.getAllScripts()) {
if (!this.scripts.exists(script.id)) { if (!this.scripts.exists(script.id)) {
const selection = new SelectedScript(script, false); const selection = new SelectedScript(script, false);
this.scripts.addItem(selection); this.scripts.addItem(selection);

View File

@@ -1,6 +1,6 @@
import { OperatingSystem } from '@/domain/OperatingSystem'; import { OperatingSystem } from '@/domain/OperatingSystem';
export interface IEnvironment { export interface IEnvironment {
isDesktop: boolean; readonly isDesktop: boolean;
os: OperatingSystem; readonly os: OperatingSystem;
} }

View File

@@ -1,42 +1,38 @@
import { Category } from '@/domain/Category';
import { Application } from '@/domain/Application';
import { IApplication } from '@/domain/IApplication'; import { IApplication } from '@/domain/IApplication';
import { IProjectInformation } from '@/domain/IProjectInformation'; import { IProjectInformation } from '@/domain/IProjectInformation';
import { ApplicationYaml } from 'js-yaml-loader!./../application.yaml'; import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { parseCategory } from './CategoryParser'; import { parseCategoryCollection } from './CategoryCollectionParser';
import { ProjectInformation } from '@/domain/ProjectInformation'; import WindowsData from 'js-yaml-loader!@/application/collections/windows.yaml';
import { ScriptCompiler } from './Compiler/ScriptCompiler'; import MacOsData from 'js-yaml-loader!@/application/collections/macos.yaml';
import { CollectionData } from 'js-yaml-loader!@/*';
import { parseProjectInformation } from '@/application/Parser/ProjectInformationParser';
import { Application } from '@/domain/Application';
export function parseApplication(
export function parseApplication(content: ApplicationYaml, env: NodeJS.ProcessEnv = process.env): IApplication { parser = CategoryCollectionParser,
validate(content); processEnv: NodeJS.ProcessEnv = process.env,
const compiler = new ScriptCompiler(content.functions); collectionsData = PreParsedCollections): IApplication {
const categories = new Array<Category>(); validateCollectionsData(collectionsData);
for (const action of content.actions) { const information = parseProjectInformation(processEnv);
const category = parseCategory(action, compiler); const collections = collectionsData.map((collection) => parser(collection, information));
categories.push(category); const app = new Application(information, collections);
}
const info = readAppInformation(env);
const app = new Application(
info,
categories);
return app; return app;
} }
function readAppInformation(environment: NodeJS.ProcessEnv): IProjectInformation { export type CategoryCollectionParserType
return new ProjectInformation( = (file: CollectionData, info: IProjectInformation) => ICategoryCollection;
environment.VUE_APP_NAME,
environment.VUE_APP_VERSION,
environment.VUE_APP_REPOSITORY_URL,
environment.VUE_APP_HOMEPAGE_URL,
);
}
function validate(content: ApplicationYaml): void { const CategoryCollectionParser: CategoryCollectionParserType
if (!content) { = (file, info) => parseCategoryCollection(file, info);
throw new Error('application is null or undefined');
const PreParsedCollections: readonly CollectionData []
= [ WindowsData, MacOsData ];
function validateCollectionsData(collections: readonly CollectionData[]) {
if (!collections.length) {
throw new Error('no collection provided');
} }
if (!content.actions || content.actions.length <= 0) { if (collections.some((collection) => !collection)) {
throw new Error('application does not define any action'); throw new Error('undefined collection provided');
} }
} }

View File

@@ -0,0 +1,39 @@
import { Category } from '@/domain/Category';
import { CollectionData } from 'js-yaml-loader!@/*';
import { parseCategory } from './CategoryParser';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { parseScriptingDefinition } from './ScriptingDefinitionParser';
import { createEnumParser } from '../Common/Enum';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
import { CategoryCollection } from '@/domain/CategoryCollection';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
export function parseCategoryCollection(
content: CollectionData,
info: IProjectInformation,
osParser = createEnumParser(OperatingSystem)): ICategoryCollection {
validate(content);
const scripting = parseScriptingDefinition(content.scripting, info);
const context = new CategoryCollectionParseContext(content.functions, scripting);
const categories = new Array<Category>();
for (const action of content.actions) {
const category = parseCategory(action, context);
categories.push(category);
}
const os = osParser.parseEnum(content.os, 'os');
const collection = new CategoryCollection(
os,
categories,
scripting);
return collection;
}
function validate(content: CollectionData): void {
if (!content) {
throw new Error('content is null or undefined');
}
if (!content.actions || content.actions.length <= 0) {
throw new Error('content does not define any action');
}
}

View File

@@ -1,9 +1,9 @@
import { YamlCategory, YamlScript } from 'js-yaml-loader!./application.yaml'; import { CategoryData, ScriptData, CategoryOrScriptData } from 'js-yaml-loader!@/*';
import { Script } from '@/domain/Script'; import { Script } from '@/domain/Script';
import { Category } from '@/domain/Category'; import { Category } from '@/domain/Category';
import { parseDocUrls } from './DocumentationParser'; import { parseDocUrls } from './DocumentationParser';
import { parseScript } from './ScriptParser'; import { ICategoryCollectionParseContext } from './Script/ICategoryCollectionParseContext';
import { IScriptCompiler } from './Compiler/IScriptCompiler'; import { parseScript } from './Script/ScriptParser';
let categoryIdCounter: number = 0; let categoryIdCounter: number = 0;
@@ -12,17 +12,15 @@ interface ICategoryChildren {
subScripts: Script[]; subScripts: Script[];
} }
export function parseCategory(category: YamlCategory, compiler: IScriptCompiler): Category { export function parseCategory(category: CategoryData, context: ICategoryCollectionParseContext): Category {
if (!compiler) { if (!context) { throw new Error('undefined context'); }
throw new Error('undefined compiler');
}
ensureValid(category); ensureValid(category);
const children: ICategoryChildren = { const children: ICategoryChildren = {
subCategories: new Array<Category>(), subCategories: new Array<Category>(),
subScripts: new Array<Script>(), subScripts: new Array<Script>(),
}; };
for (const categoryOrScript of category.children) { for (const data of category.children) {
parseCategoryChild(categoryOrScript, children, category, compiler); parseCategoryChild(data, children, category, context);
} }
return new Category( return new Category(
/*id*/ categoryIdCounter++, /*id*/ categoryIdCounter++,
@@ -33,12 +31,12 @@ export function parseCategory(category: YamlCategory, compiler: IScriptCompiler)
); );
} }
function ensureValid(category: YamlCategory) { function ensureValid(category: CategoryData) {
if (!category) { if (!category) {
throw Error('category is null or undefined'); throw Error('category is null or undefined');
} }
if (!category.children || category.children.length === 0) { if (!category.children || category.children.length === 0) {
throw Error('category has no children'); throw Error(`category has no children: "${category.category}"`);
} }
if (!category.category || category.category.length === 0) { if (!category.category || category.category.length === 0) {
throw Error('category has no name'); throw Error('category has no name');
@@ -46,28 +44,28 @@ function ensureValid(category: YamlCategory) {
} }
function parseCategoryChild( function parseCategoryChild(
categoryOrScript: any, data: CategoryOrScriptData,
children: ICategoryChildren, children: ICategoryChildren,
parent: YamlCategory, parent: CategoryData,
compiler: IScriptCompiler) { context: ICategoryCollectionParseContext) {
if (isCategory(categoryOrScript)) { if (isCategory(data)) {
const subCategory = parseCategory(categoryOrScript as YamlCategory, compiler); const subCategory = parseCategory(data as CategoryData, context);
children.subCategories.push(subCategory); children.subCategories.push(subCategory);
} else if (isScript(categoryOrScript)) { } else if (isScript(data)) {
const yamlScript = categoryOrScript as YamlScript; const scriptData = data as ScriptData;
const script = parseScript(yamlScript, compiler); const script = parseScript(scriptData, context);
children.subScripts.push(script); children.subScripts.push(script);
} else { } else {
throw new Error(`Child element is neither a category or a script. throw new Error(`Child element is neither a category or a script.
Parent: ${parent.category}, element: ${JSON.stringify(categoryOrScript)}`); Parent: ${parent.category}, element: ${JSON.stringify(data)}`);
} }
} }
function isScript(categoryOrScript: any): boolean { function isScript(data: any): boolean {
return (categoryOrScript.code && categoryOrScript.code.length > 0) return (data.code && data.code.length > 0)
|| categoryOrScript.call; || data.call;
} }
function isCategory(categoryOrScript: any): boolean { function isCategory(data: any): boolean {
return categoryOrScript.category && categoryOrScript.category.length > 0; return data.category && data.category.length > 0;
} }

View File

@@ -1,7 +0,0 @@
import { IScriptCode } from '@/domain/IScriptCode';
import { YamlScript } from 'js-yaml-loader!./application.yaml';
export interface IScriptCompiler {
canCompile(script: YamlScript): boolean;
compile(script: YamlScript): IScriptCode;
}

View File

@@ -1,6 +1,6 @@
import { YamlDocumentable, DocumentationUrls } from 'js-yaml-loader!./application.yaml'; import { DocumentableData, DocumentationUrlsData } from 'js-yaml-loader!@/*';
export function parseDocUrls(documentable: YamlDocumentable): ReadonlyArray<string> { export function parseDocUrls(documentable: DocumentableData): ReadonlyArray<string> {
if (!documentable) { if (!documentable) {
throw new Error('documentable is null or undefined'); throw new Error('documentable is null or undefined');
} }
@@ -13,7 +13,7 @@ export function parseDocUrls(documentable: YamlDocumentable): ReadonlyArray<stri
return result.getAll(); return result.getAll();
} }
function addDocs(docs: DocumentationUrls, urls: DocumentationUrlContainer): DocumentationUrlContainer { function addDocs(docs: DocumentationUrlsData, urls: DocumentationUrlContainer): DocumentationUrlContainer {
if (docs instanceof Array) { if (docs instanceof Array) {
urls.addUrls(docs); urls.addUrls(docs);
} else if (typeof docs === 'string') { } else if (typeof docs === 'string') {
@@ -32,7 +32,7 @@ class DocumentationUrlContainer {
this.urls.push(url); this.urls.push(url);
} }
public addUrls(urls: any[]) { public addUrls(urls: readonly any[]) {
for (const url of urls) { for (const url of urls) {
if (typeof url !== 'string') { if (typeof url !== 'string') {
throw new Error('Docs field (documentation url) must be an array of strings'); throw new Error('Docs field (documentation url) must be an array of strings');

View File

@@ -0,0 +1,12 @@
import { IProjectInformation } from '@/domain/IProjectInformation';
import { ProjectInformation } from '@/domain/ProjectInformation';
export function parseProjectInformation(
environment: NodeJS.ProcessEnv): IProjectInformation {
return new ProjectInformation(
environment.VUE_APP_NAME,
environment.VUE_APP_VERSION,
environment.VUE_APP_REPOSITORY_URL,
environment.VUE_APP_HOMEPAGE_URL,
);
}

View File

@@ -0,0 +1,22 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { FunctionData } from 'js-yaml-loader!*';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { ScriptCompiler } from './Compiler/ScriptCompiler';
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
import { SyntaxFactory } from './Syntax/SyntaxFactory';
import { ISyntaxFactory } from './Syntax/ISyntaxFactory';
export class CategoryCollectionParseContext implements ICategoryCollectionParseContext {
public readonly compiler: IScriptCompiler;
public readonly syntax: ILanguageSyntax;
constructor(
functionsData: ReadonlyArray<FunctionData> | undefined,
scripting: IScriptingDefinition,
syntaxFactory: ISyntaxFactory = new SyntaxFactory()) {
if (!scripting) { throw new Error('undefined scripting'); }
this.syntax = syntaxFactory.create(scripting.language);
this.compiler = new ScriptCompiler(functionsData, this.syntax);
}
}

View File

@@ -0,0 +1,73 @@
export interface IILCode {
compile(): string;
getUniqueParameterNames(): string[];
substituteParameter(parameterName: string, parameterValue: string): IILCode;
}
export function generateIlCode(rawText: string): IILCode {
const ilCode = generateIl(rawText);
return new ILCode(ilCode);
}
class ILCode implements IILCode {
private readonly ilCode: string;
constructor(ilCode: string) {
this.ilCode = ilCode;
}
public substituteParameter(parameterName: string, parameterValue: string): IILCode {
const newCode = substituteParameter(this.ilCode, parameterName, parameterValue);
return new ILCode(newCode);
}
public getUniqueParameterNames(): string[] {
return getUniqueParameterNames(this.ilCode);
}
public compile(): string {
ensureNoExpressionLeft(this.ilCode);
return this.ilCode;
}
}
// Trim each expression and put them inside "{{exp|}}" e.g. "{{ $hello }}" becomes "{{exp|$hello}}"
function generateIl(rawText: string): string {
return rawText.replace(/\{\{([\s]*[^;\s\{]+[\s]*)\}\}/g, (_, match) => {
return `\{\{exp|${match.trim()}\}\}`;
});
}
// finds all "{{exp|..}} left"
function ensureNoExpressionLeft(ilCode: string) {
const allSubstitutions = ilCode.matchAll(/\{\{exp\|(.*?)\}\}/g);
const allMatches = Array.from(allSubstitutions, (match) => match[1]);
const uniqueExpressions = getDistinctValues(allMatches);
if (uniqueExpressions.length > 0) {
throw new Error(`unknown expression: ${printList(uniqueExpressions)}`);
}
}
// Parses all distinct usages of {{exp|$parameterName}}
function getUniqueParameterNames(ilCode: string) {
const allSubstitutions = ilCode.matchAll(/\{\{exp\|\$([^;\s\{]+[\s]*)\}\}/g);
const allParameters = Array.from(allSubstitutions, (match) => match[1]);
const uniqueParameterNames = getDistinctValues(allParameters);
return uniqueParameterNames;
}
// substitutes {{exp|$parameterName}} to value of the parameter
function substituteParameter(ilCode: string, parameterName: string, parameterValue: string) {
const pattern = `{{exp|$${parameterName}}}`;
return ilCode.split(pattern).join(parameterValue); // as .replaceAll() is not yet supported by TS
}
function getDistinctValues(values: readonly string[]): string[] {
return values.filter((value, index, self) => {
return self.indexOf(value) === index;
});
}
function printList(list: readonly string[]): string {
return `"${list.join('","')}"`;
}

View File

@@ -0,0 +1,7 @@
import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptData } from 'js-yaml-loader!@/*';
export interface IScriptCompiler {
canCompile(script: ScriptData): boolean;
compile(script: ScriptData): IScriptCode;
}

View File

@@ -1,7 +1,9 @@
import { generateIlCode, IILCode } from './ILCode';
import { IScriptCode } from '@/domain/IScriptCode'; import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCode } from '@/domain/ScriptCode'; import { ScriptCode } from '@/domain/ScriptCode';
import { YamlScript, YamlFunction, FunctionCall, ScriptFunctionCall, FunctionCallParameters } from 'js-yaml-loader!./application.yaml'; import { ScriptData, FunctionData, FunctionCallData, ScriptFunctionCallData, FunctionCallParametersData } from 'js-yaml-loader!@/*';
import { IScriptCompiler } from './IScriptCompiler'; import { IScriptCompiler } from './IScriptCompiler';
import { ILanguageSyntax } from '@/domain/ScriptCode';
interface ICompiledCode { interface ICompiledCode {
readonly code: string; readonly code: string;
@@ -9,16 +11,19 @@ interface ICompiledCode {
} }
export class ScriptCompiler implements IScriptCompiler { export class ScriptCompiler implements IScriptCompiler {
constructor(private readonly functions: readonly YamlFunction[]) { constructor(
private readonly functions: readonly FunctionData[] | undefined,
private syntax: ILanguageSyntax) {
ensureValidFunctions(functions); ensureValidFunctions(functions);
if (!syntax) { throw new Error('undefined syntax'); }
} }
public canCompile(script: YamlScript): boolean { public canCompile(script: ScriptData): boolean {
if (!script.call) { if (!script.call) {
return false; return false;
} }
return true; return true;
} }
public compile(script: YamlScript): IScriptCode { public compile(script: ScriptData): IScriptCode {
this.ensureCompilable(script.call); this.ensureCompilable(script.call);
const compiledCodes = new Array<ICompiledCode>(); const compiledCodes = new Array<ICompiledCode>();
const calls = getCallSequence(script.call); const calls = getCallSequence(script.call);
@@ -32,18 +37,17 @@ export class ScriptCompiler implements IScriptCompiler {
compiledCodes.push(functionCode); compiledCodes.push(functionCode);
}); });
const scriptCode = merge(compiledCodes); const scriptCode = merge(compiledCodes);
return new ScriptCode(script.name, scriptCode.code, scriptCode.revertCode); return new ScriptCode(scriptCode.code, scriptCode.revertCode, script.name, this.syntax);
} }
private getFunctionByName(name: string): YamlFunction { private getFunctionByName(name: string): FunctionData {
const func = this.functions.find((f) => f.name === name); const func = this.functions.find((f) => f.name === name);
if (!func) { if (!func) {
throw new Error(`called function is not defined "${name}"`); throw new Error(`called function is not defined "${name}"`);
} }
return func; return func;
} }
private ensureCompilable(call: ScriptFunctionCallData) {
private ensureCompilable(call: ScriptFunctionCall) {
if (!this.functions || this.functions.length === 0) { if (!this.functions || this.functions.length === 0) {
throw new Error('cannot compile without shared functions'); throw new Error('cannot compile without shared functions');
} }
@@ -61,15 +65,19 @@ function printList(list: readonly string[]): string {
return `"${list.join('","')}"`; return `"${list.join('","')}"`;
} }
function ensureNoDuplicatesInFunctionNames(functions: readonly YamlFunction[]) { function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
const duplicateFunctionNames = getDuplicates(functions const duplicateFunctionNames = getDuplicates(functions
.map((func) => func.name.toLowerCase())); .map((func) => func.name.toLowerCase()));
if (duplicateFunctionNames.length) { if (duplicateFunctionNames.length) {
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`); throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
} }
} }
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
function ensureNoDuplicatesInParameterNames(functions: readonly YamlFunction[]) { if (functions.some((func) => !func)) {
throw new Error(`some functions are undefined`);
}
}
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
const functionsWithParameters = functions const functionsWithParameters = functions
.filter((func) => func.parameters && func.parameters.length > 0); .filter((func) => func.parameters && func.parameters.length > 0);
for (const func of functionsWithParameters) { for (const func of functionsWithParameters) {
@@ -80,7 +88,7 @@ function ensureNoDuplicatesInParameterNames(functions: readonly YamlFunction[])
} }
} }
function ensureNoDuplicateCode(functions: readonly YamlFunction[]) { function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
const duplicateCodes = getDuplicates(functions.map((func) => func.code)); const duplicateCodes = getDuplicates(functions.map((func) => func.code));
if (duplicateCodes.length > 0) { if (duplicateCodes.length > 0) {
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`); throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
@@ -93,10 +101,11 @@ function ensureNoDuplicateCode(functions: readonly YamlFunction[]) {
} }
} }
function ensureValidFunctions(functions: readonly YamlFunction[]) { function ensureValidFunctions(functions: readonly FunctionData[]) {
if (!functions) { if (!functions || functions.length === 0) {
return; return;
} }
ensureNoUndefinedItem(functions);
ensureNoDuplicatesInFunctionNames(functions); ensureNoDuplicatesInFunctionNames(functions);
ensureNoDuplicatesInParameterNames(functions); ensureNoDuplicatesInParameterNames(functions);
ensureNoDuplicateCode(functions); ensureNoDuplicateCode(functions);
@@ -117,33 +126,35 @@ function merge(codes: readonly ICompiledCode[]): ICompiledCode {
}; };
} }
function compileCode(func: YamlFunction, parameters: FunctionCallParameters): ICompiledCode { function compileCode(func: FunctionData, parameters: FunctionCallParametersData): ICompiledCode {
return { return {
code: compileExpressions(func.code, parameters), code: compileExpressions(func.code, parameters),
revertCode: compileExpressions(func.revertCode, parameters), revertCode: compileExpressions(func.revertCode, parameters),
}; };
} }
function compileExpressions(code: string, parameters: FunctionCallParameters): string { function compileExpressions(code: string, parameters: FunctionCallParametersData): string {
let intermediateCode = compileToIL(code); let intermediateCode = generateIlCode(code);
intermediateCode = substituteParameters(intermediateCode, parameters); intermediateCode = substituteParameters(intermediateCode, parameters);
ensureNoExpressionLeft(intermediateCode); return intermediateCode.compile();
return intermediateCode;
} }
function substituteParameters(intermediateCode: string, parameters: FunctionCallParameters): string { function substituteParameters(intermediateCode: IILCode, parameters: FunctionCallParametersData): IILCode {
const parameterNames = getUniqueParameterNamesFromIL(intermediateCode); const parameterNames = intermediateCode.getUniqueParameterNames();
if (parameterNames.length && !parameters) { if (parameterNames.length && !parameters) {
throw new Error(`no parameters defined, expected: ${printList(parameterNames)}`); throw new Error(`no parameters defined, expected: ${printList(parameterNames)}`);
} }
for (const parameterName of parameterNames) { for (const parameterName of parameterNames) {
const parameterValue = parameters[parameterName]; const parameterValue = parameters[parameterName];
intermediateCode = substituteParameter(intermediateCode, parameterName, parameterValue); if (!parameterValue) {
throw Error(`parameter value is not provided for "${parameterName}" in function call`);
}
intermediateCode = intermediateCode.substituteParameter(parameterName, parameterValue);
} }
return intermediateCode; return intermediateCode;
} }
function ensureValidCall(call: FunctionCall, scriptName: string) { function ensureValidCall(call: FunctionCallData, scriptName: string) {
if (!call) { if (!call) {
throw new Error(`undefined function call in script "${scriptName}"`); throw new Error(`undefined function call in script "${scriptName}"`);
} }
@@ -152,49 +163,9 @@ function ensureValidCall(call: FunctionCall, scriptName: string) {
} }
} }
function getCallSequence(call: ScriptFunctionCall): FunctionCall[] { function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
if (call instanceof Array) { if (call instanceof Array) {
return call as FunctionCall[]; return call as FunctionCallData[];
}
return [ call as FunctionCall ];
}
function getDistinctValues(values: readonly string[]): string[] {
return values.filter((value, index, self) => {
return self.indexOf(value) === index;
});
}
// Trim each expression and put them inside "{{exp|}}" e.g. "{{ $hello }}" becomes "{{exp|$hello}}"
function compileToIL(code: string) {
return code.replace(/\{\{([\s]*[^;\s\{]+[\s]*)\}\}/g, (_, match) => {
return `\{\{exp|${match.trim()}\}\}`;
});
}
// Parses all distinct usages of {{exp|$parameterName}}
function getUniqueParameterNamesFromIL(ilCode: string) {
const allSubstitutions = ilCode.matchAll(/\{\{exp\|\$([^;\s\{]+[\s]*)\}\}/g);
const allParameters = Array.from(allSubstitutions, (match) => match[1]);
const uniqueParameterNames = getDistinctValues(allParameters);
return uniqueParameterNames;
}
// substitutes {{exp|$parameterName}} to value of the parameter
function substituteParameter(ilCode: string, parameterName: string, parameterValue: string) {
if (!parameterValue) {
throw Error(`parameter value is not provided for "${parameterName}" in function call`);
}
const pattern = `{{exp|$${parameterName}}}`;
return ilCode.split(pattern).join(parameterValue); // as .replaceAll() is not yet supported by TS
}
// finds all "{{exp|..}} left"
function ensureNoExpressionLeft(ilCode: string) {
const allSubstitutions = ilCode.matchAll(/\{\{exp\|(.*?)\}\}/g);
const allMatches = Array.from(allSubstitutions, (match) => match[1]);
const uniqueExpressions = getDistinctValues(allMatches);
if (uniqueExpressions.length > 0) {
throw new Error(`unknown expression: ${printList(uniqueExpressions)}`);
} }
return [ call as FunctionCallData ];
} }

View File

@@ -0,0 +1,7 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
export interface ICategoryCollectionParseContext {
readonly compiler: IScriptCompiler;
readonly syntax: ILanguageSyntax;
}

View File

@@ -0,0 +1,54 @@
import { Script } from '@/domain/Script';
import { ScriptData } from 'js-yaml-loader!@/*';
import { parseDocUrls } from '../DocumentationParser';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCode } from '@/domain/ScriptCode';
import { createEnumParser, IEnumParser } from '../../Common/Enum';
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
export function parseScript(
data: ScriptData, context: ICategoryCollectionParseContext,
levelParser = createEnumParser(RecommendationLevel)): Script {
validateScript(data);
if (!context) { throw new Error('undefined context'); }
const script = new Script(
/* name */ data.name,
/* code */ parseCode(data, context),
/* docs */ parseDocUrls(data),
/* level */ parseLevel(data.recommend, levelParser));
return script;
}
function parseLevel(level: string, parser: IEnumParser<RecommendationLevel>): RecommendationLevel | undefined {
if (!level) {
return undefined;
}
return parser.parseEnum(level, 'level');
}
function parseCode(script: ScriptData, context: ICategoryCollectionParseContext): IScriptCode {
if (context.compiler.canCompile(script)) {
return context.compiler.compile(script);
}
return new ScriptCode(script.code, script.revertCode, script.name, context.syntax);
}
function ensureNotBothCallAndCode(script: ScriptData) {
if (script.code && script.call) {
throw new Error('cannot define both "call" and "code"');
}
if (script.revertCode && script.call) {
throw new Error('cannot define "revertCode" if "call" is defined');
}
}
function validateScript(script: ScriptData) {
if (!script) {
throw new Error('undefined script');
}
if (!script.code && !script.call) {
throw new Error('must define either "call" or "code"');
}
ensureNotBothCallAndCode(script);
}

View File

@@ -0,0 +1,6 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
export class BatchFileSyntax implements ILanguageSyntax {
public readonly commentDelimiters = [ 'REM', '::' ];
public readonly commonCodeParts = [ '(', ')', 'else' ];
}

View File

@@ -0,0 +1,6 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
export interface ISyntaxFactory {
create(language: ScriptingLanguage): ILanguageSyntax;
}

View File

@@ -0,0 +1,6 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
export class ShellScriptSyntax implements ILanguageSyntax {
public readonly commentDelimiters = [ '#' ];
public readonly commonCodeParts = [ '(', ')', 'else' ];
}

View File

@@ -0,0 +1,15 @@
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { ISyntaxFactory } from './ISyntaxFactory';
import { BatchFileSyntax } from './BatchFileSyntax';
import { ShellScriptSyntax } from './ShellScriptSyntax';
export class SyntaxFactory implements ISyntaxFactory {
public create(language: ScriptingLanguage): ILanguageSyntax {
switch (language) {
case ScriptingLanguage.batchfile: return new BatchFileSyntax();
case ScriptingLanguage.shellscript: return new ShellScriptSyntax();
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
}
}
}

View File

@@ -1,61 +0,0 @@
import { Script } from '@/domain/Script';
import { YamlScript } from 'js-yaml-loader!./application.yaml';
import { parseDocUrls } from './DocumentationParser';
import { RecommendationLevelNames, RecommendationLevel } from '@/domain/RecommendationLevel';
import { IScriptCompiler } from './Compiler/IScriptCompiler';
import { IScriptCode } from '@/domain/IScriptCode';
import { ScriptCode } from '@/domain/ScriptCode';
export function parseScript(yamlScript: YamlScript, compiler: IScriptCompiler): Script {
validateScript(yamlScript);
if (!compiler) {
throw new Error('undefined compiler');
}
const script = new Script(
/* name */ yamlScript.name,
/* code */ parseCode(yamlScript, compiler),
/* docs */ parseDocUrls(yamlScript),
/* level */ getLevel(yamlScript.recommend));
return script;
}
function getLevel(level: string): RecommendationLevel | undefined {
if (!level) {
return undefined;
}
if (typeof level !== 'string') {
throw new Error(`level must be a string but it was ${typeof level}`);
}
const typedLevel = RecommendationLevelNames
.find((l) => l.toLowerCase() === level.toLowerCase());
if (!typedLevel) {
throw new Error(`unknown level: \"${level}\"`);
}
return RecommendationLevel[typedLevel as keyof typeof RecommendationLevel];
}
function parseCode(yamlScript: YamlScript, compiler: IScriptCompiler): IScriptCode {
if (compiler.canCompile(yamlScript)) {
return compiler.compile(yamlScript);
}
return new ScriptCode(yamlScript.name, yamlScript.code, yamlScript.revertCode);
}
function ensureNotBothCallAndCode(yamlScript: YamlScript) {
if (yamlScript.code && yamlScript.call) {
throw new Error('cannot define both "call" and "code"');
}
if (yamlScript.revertCode && yamlScript.call) {
throw new Error('cannot define "revertCode" if "call" is defined');
}
}
function validateScript(yamlScript: YamlScript) {
if (!yamlScript) {
throw new Error('undefined script');
}
if (!yamlScript.code && !yamlScript.call) {
throw new Error('must define either "call" or "code"');
}
ensureNotBothCallAndCode(yamlScript);
}

View File

@@ -0,0 +1,36 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { ScriptingDefinitionData } from 'js-yaml-loader!@/*';
import { ScriptingDefinition } from '@/domain/ScriptingDefinition';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IProjectInformation } from '@/domain/IProjectInformation';
import { createEnumParser } from '../Common/Enum';
import { generateIlCode } from './Script/Compiler/ILCode';
export function parseScriptingDefinition(
definition: ScriptingDefinitionData,
info: IProjectInformation,
date = new Date(),
languageParser = createEnumParser(ScriptingLanguage)): IScriptingDefinition {
if (!info) {
throw new Error('undefined info');
}
if (!definition) {
throw new Error('undefined definition');
}
const language = languageParser.parseEnum(definition.language, 'language');
const startCode = applySubstitutions(definition.startCode, info, date);
const endCode = applySubstitutions(definition.endCode, info, date);
return new ScriptingDefinition(
language,
startCode,
endCode,
);
}
function applySubstitutions(code: string, info: IProjectInformation, date: Date): string {
let ilCode = generateIlCode(code);
ilCode = ilCode.substituteParameter('homepage', info.homepage);
ilCode = ilCode.substituteParameter('version', info.version);
ilCode = ilCode.substituteParameter('date', date.toUTCString());
return ilCode.compile();
}

View File

@@ -1,45 +0,0 @@
import { UserFilter } from './Filter/UserFilter';
import { IUserFilter } from './Filter/IUserFilter';
import { ApplicationCode } from './Code/ApplicationCode';
import { UserSelection } from './Selection/UserSelection';
import { IUserSelection } from './Selection/IUserSelection';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { Signal } from '@/infrastructure/Events/Signal';
import { parseApplication } from '../Parser/ApplicationParser';
import { IApplicationState } from './IApplicationState';
import { Script } from '@/domain/Script';
import { IApplication } from '@/domain/IApplication';
import { IApplicationCode } from './Code/IApplicationCode';
import applicationFile from 'js-yaml-loader!@/application/application.yaml';
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
/** Mutatable singleton application state that's the single source of truth throughout the application */
export class ApplicationState implements IApplicationState {
/** Get singleton application state */
public static GetAsync(): Promise<IApplicationState> {
return ApplicationState.instance.getValueAsync();
}
/** Application instance with all scripts. */
private static instance = new AsyncLazy<IApplicationState>(() => {
const application = parseApplication(applicationFile);
const selectedScripts = new Array<Script>();
const state = new ApplicationState(application, selectedScripts);
return Promise.resolve(state);
});
public readonly code: IApplicationCode;
public readonly stateChanged = new Signal<IApplicationState>();
public readonly selection: IUserSelection;
public readonly filter: IUserFilter;
private constructor(
/** Inner instance of the all scripts */
public readonly app: IApplication,
/** Initially selected scripts */
public readonly defaultScripts: Script[]) {
this.selection = new UserSelection(app, defaultScripts.map((script) => new SelectedScript(script, false)));
this.code = new ApplicationCode(this.selection, app.info.version);
this.filter = new UserFilter(app);
}
}

View File

@@ -1,7 +0,0 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition';
export interface IUserScript {
code: string;
scriptPositions: Map<SelectedScript, ICodePosition>;
}

View File

@@ -1,7 +0,0 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserScript } from './IUserScript';
export interface IUserScriptGenerator {
buildCode(
selectedScripts: ReadonlyArray<SelectedScript>,
version: string): IUserScript;
}

View File

@@ -1,68 +0,0 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript';
import { IUserScriptGenerator } from './IUserScriptGenerator';
import { CodeBuilder } from './CodeBuilder';
import { ICodePosition } from '@/application/State/Code/Position/ICodePosition';
import { CodePosition } from '../Position/CodePosition';
import { IUserScript } from './IUserScript';
export const adminRightsScript = {
name: 'Ensure admin privileges',
code: 'fltmc >nul 2>&1 || (\n' +
' echo Administrator privileges are required.\n' +
' PowerShell Start -Verb RunAs \'%0\' 2> nul || (\n' +
' echo Right-click on the script and select "Run as administrator".\n' +
' pause & exit 1\n' +
' )\n' +
' exit 0\n' +
')',
};
export class UserScriptGenerator implements IUserScriptGenerator {
public buildCode(selectedScripts: ReadonlyArray<SelectedScript>, version: string): IUserScript {
if (!selectedScripts) { throw new Error('scripts is undefined'); }
if (!version) { throw new Error('version is undefined'); }
let scriptPositions = new Map<SelectedScript, ICodePosition>();
if (!selectedScripts.length) {
return { code: '', scriptPositions };
}
const builder = initializeCode(version);
for (const selection of selectedScripts) {
scriptPositions = appendSelection(selection, scriptPositions, builder);
}
const code = finalizeCode(builder);
return { code, scriptPositions };
}
}
function initializeCode(version: string): CodeBuilder {
return new CodeBuilder()
.appendLine('@echo off')
.appendCommentLine(`https://privacy.sexy — v${version}${new Date().toUTCString()}`)
.appendFunction(adminRightsScript.name, adminRightsScript.code)
.appendLine();
}
function finalizeCode(builder: CodeBuilder): string {
return builder.appendLine()
.appendLine('pause')
.appendLine('exit /b 0')
.toString();
}
function appendSelection(
selection: SelectedScript,
scriptPositions: Map<SelectedScript, ICodePosition>,
builder: CodeBuilder): Map<SelectedScript, ICodePosition> {
const startPosition = builder.currentLine + 1;
appendCode(selection, builder);
const endPosition = builder.currentLine - 1;
builder.appendLine();
scriptPositions.set(selection, new CodePosition(startPosition, endPosition));
return scriptPositions;
}
function appendCode(selection: SelectedScript, builder: CodeBuilder) {
const name = selection.revert ? `${selection.script.name} (revert)` : selection.script.name;
const scriptCode = selection.revert ? selection.script.code.revert : selection.script.code.execute;
builder.appendFunction(name, scriptCode);
}

View File

@@ -1,15 +0,0 @@
import { IApplication } from './../../domain/IApplication';
import { IUserFilter } from './Filter/IUserFilter';
import { IUserSelection } from './Selection/IUserSelection';
import { ISignal } from '@/infrastructure/Events/ISignal';
import { IApplicationCode } from './Code/IApplicationCode';
export { IUserSelection, IApplicationCode, IUserFilter };
export interface IApplicationState {
/** Event that fires when the application states changes with new application state as parameter */
readonly code: IApplicationCode;
readonly filter: IUserFilter;
readonly stateChanged: ISignal<IApplicationState>;
readonly selection: IUserSelection;
readonly app: IApplication;
}

View File

@@ -1,47 +0,0 @@
declare module 'js-yaml-loader!*' {
export interface ApplicationYaml {
actions: ReadonlyArray<YamlCategory>;
functions: ReadonlyArray<YamlFunction> | undefined;
}
export interface YamlCategory extends YamlDocumentable {
children: ReadonlyArray<CategoryOrScript>;
category: string;
}
export type CategoryOrScript = YamlCategory | YamlScript;
export type DocumentationUrls = ReadonlyArray<string> | string;
export interface YamlDocumentable {
docs?: DocumentationUrls;
}
export interface YamlFunction {
name: string;
code: string;
revertCode?: string;
parameters?: readonly string[];
}
export interface FunctionCallParameters {
[index: string]: string;
}
export interface FunctionCall {
function: string;
parameters?: FunctionCallParameters;
}
export type ScriptFunctionCall = readonly FunctionCall[] | FunctionCall | undefined;
export interface YamlScript extends YamlDocumentable {
name: string;
code: string | undefined;
revertCode: string | undefined;
call: ScriptFunctionCall;
recommend: string | undefined;
}
const content: ApplicationYaml;
export default content;
}

View File

@@ -0,0 +1,56 @@
declare module 'js-yaml-loader!*' {
export interface CollectionData {
readonly os: string;
readonly scripting: ScriptingDefinitionData;
readonly actions: ReadonlyArray<CategoryData>;
readonly functions?: ReadonlyArray<FunctionData>;
}
export interface CategoryData extends DocumentableData {
readonly children: ReadonlyArray<CategoryOrScriptData>;
readonly category: string;
}
export type CategoryOrScriptData = CategoryData | ScriptData;
export type DocumentationUrlsData = ReadonlyArray<string> | string;
export interface DocumentableData {
readonly docs?: DocumentationUrlsData;
}
export interface FunctionData {
name: string;
code: string;
revertCode?: string;
parameters?: readonly string[];
}
export interface FunctionCallParametersData {
[index: string]: string;
}
export interface FunctionCallData {
function: string;
parameters?: FunctionCallParametersData;
}
export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined;
export interface ScriptData extends DocumentableData {
name: string;
code?: string;
revertCode?: string;
call: ScriptFunctionCallData;
recommend?: string;
}
export interface ScriptingDefinitionData {
readonly language: string;
readonly fileExtension: string;
readonly startCode: string;
readonly endCode: string;
}
const content: CollectionData;
export default content;
}

View File

@@ -0,0 +1,225 @@
# Structure documented in "docs/collections.md"
os: macos
scripting:
language: shellscript
startCode: |-
#!/usr/bin/env bash
# {{ $homepage }} — v{{ $version }} — {{ $date }}
if [ "$EUID" -ne 0 ]; then
script_path=$([[ "$0" = /* ]] && echo "$0" || echo "$PWD/${0#./}")
sudo "$script_path" || (
echo 'Administrator privileges are required.'
exit 1
)
exit 0
fi
endCode: |-
echo 'Your privacy and security is now hardened 🎉💪'
echo 'Press any key to exit.'
read -n 1 -s
actions:
-
category: Privacy cleanup
children:
-
category: Clear terminal history
children:
-
name: Clear bash history
recommend: standard
code: rm -f ~/.bash_history
-
name: Clear zsh history
recommend: standard
code: rm -f ~/.zsh_history
-
name: Clear CUPS printer job cache
recommend: strict
code: |-
sudo rm -rfv /var/spool/cups/c0*
sudo rm -rfv /var/spool/cups/tmp/*
sudo rm -rfv /var/spool/cups/cache/job.cache*
-
name: Clear the list of iOS devices connected
recommend: strict
code: |-
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist Devices
sudo defaults delete /Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
sudo rm -rfv /var/db/lockdown/*
-
name: Reset privacy database (remove all permissions)
code: sudo tccutil reset All
-
category: Configure programs
children:
-
name: Disable Firefox telemetry
recommend: standard
docs: https://github.com/mozilla/policy-templates/blob/master/README.md
code: |-
# Enable Firefox policies so the telemetry can be configured.
sudo defaults write /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled -bool TRUE
# Disable sending usage data
sudo defaults write /Library/Preferences/org.mozilla.firefox DisableTelemetry -bool TRUE
revertCode: |-
sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled
sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry
-
name: Disable Microsoft Office diagnostics data sending
recommend: standard
code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData
revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference
-
name: Uninstall Google update
recommend: strict
code: |-
googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall
if [ -f "$googleUpdateFile" ]; then
$googleUpdateFile --nuke
echo Uninstalled google update
else
echo Google update file does not exist
fi
-
name: Disable Homebrew user behavior analytics
recommend: standard
docs: https://docs.brew.sh/Analytics
call:
-
function: PersistUserEnvironmentConfiguration
parameters:
configuration: export HOMEBREW_NO_ANALYTICS=1
-
name: Disable NET Core CLI telemetry
recommend: standard
call:
-
function: PersistUserEnvironmentConfiguration
parameters:
configuration: export DOTNET_CLI_TELEMETRY_OPTOUT=1
-
name: Disable PowerShell Core telemetry
recommend: standard
docs: https://github.com/PowerShell/PowerShell/tree/release/v7.1.1#telemetry
call:
-
function: PersistUserEnvironmentConfiguration
parameters:
configuration: export POWERSHELL_TELEMETRY_OPTOUT=1
-
category: Configure OS
children:
-
category: Configure Apple Remote Desktop
children:
-
name: Deactivate the Remote Management Service
recommend: strict
code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop
revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console
-
name: Remove Apple Remote Desktop Settings
recommend: strict
code: |-
sudo rm -rf /var/db/RemoteManagement
sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist
defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist
sudo rm -r /Library/Application\ Support/Apple/Remote\ Desktop/
rm -r ~/Library/Application\ Support/Remote\ Desktop/
rm -r ~/Library/Containers/com.apple.RemoteDesktop
-
name: Disable Internet based spell correction
code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false
revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled
-
name: Disable Remote Apple Events
recommend: strict
code: sudo systemsetup -setremoteappleevents off
revertCode: sudo systemsetup -setremoteappleevents on
-
name: Do not store documents to iCloud Drive by default
docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html
recommend: standard
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
-
category: Security improvements
children:
-
category: Configure macOS Application Firewall
children:
-
name: Enable firewall
recommend: standard
docs: https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81681
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off
-
name: Turn on firewall logging
recommend: standard
docs: https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode on
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
-
name: Turn on stealth mode
recommend: standard
docs: https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327
code: /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode on
revertCode: /usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode off
-
name: Disable Spotlight indexing
code: sudo mdutil -i off -d /
revertCode: sudo mdutil -i on /
-
name: Disable Captive portal
docs:
- https://web.archive.org/web/20171008071031if_/http://blog.erratasec.com/2010/09/apples-secret-wispr-request.html#.WdnPa5OyL6Y
- https://web.archive.org/web/20130407200745/http://www.divertednetworks.net/apple-captiveportal.html
- https://web.archive.org/web/20170622064304/https://grpugh.wordpress.com/2014/10/29/an-undocumented-change-to-captive-network-assistant-settings-in-os-x-10-10-yosemite/
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active -bool false
revertCode: sudo defaults delete /Library/Preferences/SystemConfiguration/com.apple.captive.control.plist Active
-
name: Require a password to wake the computer from sleep or screen saver
code: defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword
-
name: Do not show recent items on dock
docs: https://developer.apple.com/documentation/devicemanagement/dock
code: defaults write com.apple.dock show-recents -bool false
revertCode: defaults delete com.apple.dock show-recents
-
name: Disable AirDrop file sharing
recommend: strict
code: defaults write com.apple.NetworkBrowser DisableAirDrop -bool true
revertCode: defaults write com.apple.NetworkBrowser DisableAirDrop -bool false
functions:
-
name: PersistUserEnvironmentConfiguration
parameters: [ configuration ]
code: |-
command='{{ $configuration }}'
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
for profile_file in "${profile_files[@]}"
do
touch "$profile_file"
if ! grep -q "$command" "${profile_file}"; then
echo "$command" >> "$profile_file"
echo "[$profile_file] Configured"
else
echo "[$profile_file] No need for any action, already configured"
fi
done
revertCode: |-
command='{{ $configuration }}'
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
for profile_file in "${profile_files[@]}"
do
if grep -q "$command" "${profile_file}" 2>/dev/null; then
sed -i '' "/$command/d" "$profile_file"
echo "[$profile_file] Reverted configuration"
else
echo "[$profile_file] No need for any action, configuration does not exist"
fi
done

View File

@@ -1,4 +1,22 @@
# Structure documented in "docs/application-file.md" # Structure documented in "docs/collections.md"
os: windows
scripting:
language: batchfile
startCode: |-
@echo off
:: {{ $homepage }} — v{{ $version }} — {{ $date }}
:: Ensure admin privileges
fltmc >nul 2>&1 || (
echo Administrator privileges are required.
PowerShell Start -Verb RunAs '%0' 2> nul || (
echo Right-click on the script and select "Run as administrator".
pause & exit 1
)
exit 0
)
endCode: |-
pause
exit /b 0
actions: actions:
- -
category: Privacy cleanup category: Privacy cleanup
@@ -18,7 +36,7 @@ actions:
recommend: standard recommend: standard
code: rd /s /q "%APPDATA%\Macromedia\Flash Player" code: rd /s /q "%APPDATA%\Macromedia\Flash Player"
- -
name: Clear Steam dumps, logs and traces name: Clear Steam dumps, logs, and traces
recommend: standard recommend: standard
code: |- code: |-
del /f /q %ProgramFiles(x86)%\Steam\Dumps del /f /q %ProgramFiles(x86)%\Steam\Dumps
@@ -102,7 +120,7 @@ actions:
reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs" /va /f reg delete "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs" /va /f
reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSaveMRU" /va /f reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSaveMRU" /va /f
- -
name: Clear windows media player recent files and urls name: Clear windows media player recent files and URLs
recommend: standard recommend: standard
code: |- code: |-
reg delete "HKCU\Software\Microsoft\MediaPlayer\Player\RecentFileList" /va /f reg delete "HKCU\Software\Microsoft\MediaPlayer\Player\RecentFileList" /va /f
@@ -181,7 +199,7 @@ actions:
) )
) )
- -
name: Clear all Firefox user profiles, settings and data name: Clear all Firefox user profiles, settings, and data
code: |- code: |-
rd /s /q "%LOCALAPPDATA%\Mozilla\Firefox\Profiles" rd /s /q "%LOCALAPPDATA%\Mozilla\Firefox\Profiles"
rd /s /q "%APPDATA%\Mozilla\Firefox\Profiles" rd /s /q "%APPDATA%\Mozilla\Firefox\Profiles"
@@ -286,7 +304,7 @@ actions:
docs: https://support.microsoft.com/en-gb/help/4056823/performance-issue-with-custom-default-user-profile docs: https://support.microsoft.com/en-gb/help/4056823/performance-issue-with-custom-default-user-profile
code: del /f /q %localappdata%\Microsoft\Windows\WebCache\*.* code: del /f /q %localappdata%\Microsoft\Windows\WebCache\*.*
- -
name: Clear system temp folder when noone is logged in name: Clear system temp folder when no one is logged in
recommend: standard recommend: standard
code: del /f /q %SystemRoot%\ServiceProfiles\LocalService\AppData\Local\Temp\*.* code: del /f /q %SystemRoot%\ServiceProfiles\LocalService\AppData\Local\Temp\*.*
- -
@@ -406,6 +424,17 @@ actions:
net start DPS net start DPS
) )
endlocal endlocal
-
name: Clear previous Windows installations
code: |-
if exist "%SystemDrive%\Windows.old" (
takeown /f "%SystemDrive%\Windows.old" /a /r /d y
icacls "%SystemDrive%\Windows.old" /grant administrators:F /t
rd /s /q "%SystemDrive%\Windows.old"
echo Deleted previous installation from "%SystemDrive%\Windows.old\"
) else (
echo No previous Windows installation has been found
)
- -
category: Disable OS data collection category: Disable OS data collection
children: children:
@@ -546,13 +575,13 @@ actions:
code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 0 /f code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 1 /f revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\DriverSearching" /v "SearchOrderConfig" /t REG_DWORD /d 1 /f
- -
name: Disable cloud speech recognation name: Disable cloud speech recognition
recommend: standard recommend: standard
docs: https://www.tenforums.com/tutorials/101902-turn-off-online-speech-recognition-windows-10-a.html docs: https://www.tenforums.com/tutorials/101902-turn-off-online-speech-recognition-windows-10-a.html
code: reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy" /v "HasAccepted" /t "REG_DWORD" /d 0 /f code: reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy" /v "HasAccepted" /t "REG_DWORD" /d 0 /f
revertCode: reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy" /v "HasAccepted" /t "REG_DWORD" /d 1 /f revertCode: reg add "HKCU\Software\Microsoft\Speech_OneCore\Settings\OnlineSpeechPrivacy" /v "HasAccepted" /t "REG_DWORD" /d 1 /f
- -
name: Disable active prompting (pings to MSFT NCSI server) name: Disable active probing (pings to MSFT NCSI server)
recommend: strict recommend: strict
code: reg add "HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet" /v "EnableActiveProbing" /t REG_DWORD /d "0" /f code: reg add "HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet" /v "EnableActiveProbing" /t REG_DWORD /d "0" /f
revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet" /v "EnableActiveProbing" /t REG_DWORD /d "1" /f revertCode: reg add "HKLM\SYSTEM\CurrentControlSet\Services\NlaSvc\Parameters\Internet" /v "EnableActiveProbing" /t REG_DWORD /d "1" /f
@@ -612,7 +641,7 @@ actions:
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsAccessLocation_ForceAllowTheseApps" /f reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsAccessLocation_ForceAllowTheseApps" /f
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsAccessLocation_ForceDenyTheseApps" /f reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsAccessLocation_ForceDenyTheseApps" /f
- -
name: Deny app accesss to account info, name and picture name: Deny app access to account info, name, and picture
recommend: standard recommend: standard
docs: https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-privacy#privacy-letappsaccessaccountinfo docs: https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-privacy#privacy-letappsaccessaccountinfo
code: |- code: |-
@@ -688,7 +717,7 @@ actions:
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsAccessTrustedDevices_ForceAllowTheseApps" /f reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsAccessTrustedDevices_ForceAllowTheseApps" /f
reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsAccessTrustedDevices_ForceDenyTheseApps" /f reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" /v "LetAppsAccessTrustedDevices_ForceDenyTheseApps" /f
- -
name: Deny app sync with devices (unpaired, beacons, TVs etc.) name: Deny app sync with devices (unpaired, beacons, TVs, etc.)
recommend: standard recommend: standard
docs: https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-privacy#privacy-letappssyncwithdevices docs: https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-privacy#privacy-letappssyncwithdevices
code: |- code: |-
@@ -1006,43 +1035,99 @@ actions:
category: Disable windows search data collection category: Disable windows search data collection
children: children:
- -
name: Disable cortana category: Disable cortana
children:
-
name: Do not allow Cortana
recommend: standard
docs:
- https://admx.help/?Category=Windows_10_2016&Policy=FullArmor.Policies.3B9EA2B5_A1D1_4CD5_9EDE_75B22990BC21::AllowCortana
- https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#21-cortana-and-search-group-policies
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCortana" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCortana" /f
-
name: Do not allow Cortana experience
recommend: standard
code: reg add "HKLM\SOFTWARE\Microsoft\PolicyManager\default\Experience\AllowCortana" /v "value" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SOFTWARE\Microsoft\PolicyManager\default\Experience\AllowCortana" /v "value" /t REG_DWORD /d 1 /f
-
name: Do not allow search and Cortana to search cloud sources like OneDrive and SharePoint
recommend: standard
docs: https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-search#search-allowcloudsearch
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCloudSearch" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCloudSearch" /t REG_DWORD /d 1 /f
-
name: Disable Cortana speech interaction while the system is locked
recommend: standard
docs: https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-abovelock
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCortanaAboveLock" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCortanaAboveLock" /f
-
name: Opt out from Cortana consent
recommend: standard
code: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Search" /v "CortanaConsent" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Search" /v "CortanaConsent" /t REG_DWORD /d 10 /f
-
name: Do not allow Cortana to be enabled
recommend: standard
code: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CanCortanaBeEnabled" /t REG_DWORD /d 0 /f
revertCode: reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CanCortanaBeEnabled" /t REG_DWORD /d 1 /f
-
name: Disable Cortana (Internet search results in start menu)
recommend: standard
code: |-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 0 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 0 /f
revertCode: |-
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 1 /f
-
name: Remove the Cortana taskbar icon
recommend: standard
code: reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /t REG_DWORD /d 0 /f
revertCode: reg delete HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced /v "ShowCortanaButton" /f
-
category: Configure Windows search indexing
children:
-
name: Disable search indexing encrypted items / stores
recommend: standard
docs: https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-search#search-allowindexingencryptedstoresoritems
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowIndexingEncryptedStoresOrItems" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowIndexingEncryptedStoresOrItems" /f
-
name: Do not use automatic language detection when indexing
recommend: standard
docs: https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-search#search-alwaysuseautolangdetection
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AlwaysUseAutoLangDetection" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AlwaysUseAutoLangDetection" /t REG_DWORD /d 1 /f
-
name: Do not allow search to use location
recommend: standard recommend: standard
code: |- docs:
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCortana" /t REG_DWORD /d 0 /f - https://docs.microsoft.com/en-us/windows/client-management/mdm/policy-csp-search#search-allowsearchtouselocation
reg add "HKLM\SOFTWARE\Microsoft\PolicyManager\default\Experience\AllowCortana" /v "value" /t REG_DWORD /d 0 /f - https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#21-cortana-and-search-group-policies
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 0 /f code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowSearchToUseLocation" /t REG_DWORD /d 0 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CortanaEnabled" /t REG_DWORD /d 0 /f revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowSearchToUseLocation" /t REG_DWORD /d 1 /f
reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "CanCortanaBeEnabled" /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v BingSearchEnabled /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCloudSearch" /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCortana" /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowCortanaAboveLock" /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "AllowSearchToUseLocation" /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "ConnectedSearchUseWeb" /t REG_DWORD /d 0 /f
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Search" /v "CortanaConsent" /d 0 /t REG_DWORD /f
- -
name: Disable web search in search bar name: Disable web search in search bar
recommend: standard recommend: standard
code: |- docs:
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v DisableWebSearch /t REG_DWORD /d 1 /f - https://admx.help/?Category=Windows_10_2016&Policy=FullArmor.Policies.3B9EA2B5_A1D1_4CD5_9EDE_75B22990BC21::DisableWebSearch
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Search" /v "BingSearchEnabled" /d 0 /t REG_DWORD /f - https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#21-cortana-and-search-group-policies
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "DisableWebSearch" /t REG_DWORD /d 1 /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "DisableWebSearch" /t REG_DWORD /d 0 /f
- -
name: Disable search web when searching pc name: Do not search the web or display web results in Search
recommend: standard docs: https://docs.microsoft.com/en-us/windows/privacy/manage-connections-from-windows-operating-system-components-to-microsoft-services#21-cortana-and-search-group-policies
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v ConnectedSearchUseWeb /t REG_DWORD /d 0 /f recomend: standard
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "ConnectedSearchUseWeb" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v "ConnectedSearchUseWeb" /t REG_DWORD /d 1 /f
- -
name: Disable search indexing encrypted items / stores name: Disable Bing search
recommend: standard recommend: standard
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v AllowIndexingEncryptedStoresOrItems /t REG_DWORD /d 0 /f code: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "BingSearchEnabled" /t REG_DWORD /d 0 /f
- revertCode: reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Search" /v "BingSearchEnabled" /t REG_DWORD /d 1 /f
name: Disable location based info in searches
recommend: standard
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v AllowSearchToUseLocation /t REG_DWORD /d 0 /f
-
name: Disable language detection
recommend: standard
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\Windows Search" /v AlwaysUseAutoLangDetection /t REG_DWORD /d 0 /f
- -
category: Disable targeted ads and marketing category: Disable targeted ads and marketing
children: children:
@@ -1102,7 +1187,7 @@ actions:
reg add "HKLM\SOFTWARE\Microsoft\PolicyManager\default\WiFi\AllowAutoConnectToWiFiSenseHotspots" /v "value" /t REG_DWORD /d 0 /f reg add "HKLM\SOFTWARE\Microsoft\PolicyManager\default\WiFi\AllowAutoConnectToWiFiSenseHotspots" /v "value" /t REG_DWORD /d 0 /f
reg add "HKLM\SOFTWARE\Microsoft\WcmSvc\wifinetworkmanager\config" /v "AutoConnectAllowedOEM" /t REG_DWORD /d 0 /f reg add "HKLM\SOFTWARE\Microsoft\WcmSvc\wifinetworkmanager\config" /v "AutoConnectAllowedOEM" /t REG_DWORD /d 0 /f
- -
name: Disable App Launch Tracking name: Hide most used apps (tracks app launch)
docs: https://www.thewindowsclub.com/enable-or-disable-app-launch-tracking-in-windows-10 docs: https://www.thewindowsclub.com/enable-or-disable-app-launch-tracking-in-windows-10
recommend: strict recommend: strict
code: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "Start_TrackProgs" /d 0 /t REG_DWORD /f code: reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "Start_TrackProgs" /d 0 /t REG_DWORD /f
@@ -1558,6 +1643,30 @@ actions:
- -
category: Configure Edge category: Configure Edge
children: children:
-
category: Chromium Edge settings
children:
-
name: Disable Edge usage and crash-related data reporting # Obselete since Microsoft Edge version 89
recommend: standard
docs:
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::MetricsReportingEnabled
- https://docs.microsoft.com/en-us/DeployEdge/microsoft-edge-policies#metricsreportingenabled
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "MetricsReportingEnabled" /f
-
name: Disable sending site information # Obselete since Microsoft Edge version 89
recommend: standard
docs:
- https://admx.help/?Category=EdgeChromium&Policy=Microsoft.Policies.Edge::SendSiteInfoToImproveServices
- https://docs.microsoft.com/en-us/DeployEdge/microsoft-edge-policies#sendsiteinfotoimproveservices
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /t REG_DWORD /d 0 /f
revertCode: reg delete "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "SendSiteInfoToImproveServices" /f
-
name: Disable Automatic Installation of Microsoft Edge Chromium
docs: https://docs.microsoft.com/en-us/deployedge/microsoft-edge-blocker-toolkit
code: reg add "HKLM\SOFTWARE\Microsoft\EdgeUpdate" /v "DoNotUpdateToEdgeWithChromium" /t REG_DWORD /d 1 /f
revertCode: reg delete "HKLM\SOFTWARE\Microsoft\EdgeUpdate" /v "DoNotUpdateToEdgeWithChromium" /f
- -
name: Disable live tile data collection name: Disable live tile data collection
recommend: standard recommend: standard
@@ -1592,11 +1701,6 @@ actions:
recommend: standard recommend: standard
code: reg add "HKLM\SOFTWARE\Policies\Microsoft\MicrosoftEdge\SearchScopes" /v "ShowSearchSuggestionsGlobal" /t REG_DWORD /d 0 /f code: reg add "HKLM\SOFTWARE\Policies\Microsoft\MicrosoftEdge\SearchScopes" /v "ShowSearchSuggestionsGlobal" /t REG_DWORD /d 0 /f
revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\MicrosoftEdge\SearchScopes" /v "ShowSearchSuggestionsGlobal" /t REG_DWORD /d 1 /f revertCode: reg add "HKLM\SOFTWARE\Policies\Microsoft\MicrosoftEdge\SearchScopes" /v "ShowSearchSuggestionsGlobal" /t REG_DWORD /d 1 /f
-
name: Disable Automatic Installation of Microsoft Edge Chromium
docs: https://docs.microsoft.com/en-us/deployedge/microsoft-edge-blocker-toolkit
code: reg add "HKLM\SOFTWARE\Microsoft\EdgeUpdate" /v "DoNotUpdateToEdgeWithChromium" /t REG_DWORD /d 1 /f
revertCode: reg delete "HKLM\SOFTWARE\Microsoft\EdgeUpdate" /v "DoNotUpdateToEdgeWithChromium" /f
- -
category: Configure Internet Explorer category: Configure Internet Explorer
children: children:
@@ -1649,7 +1753,7 @@ actions:
category: Chrome cleanup category: Chrome cleanup
children: children:
- -
name: Do not share share scanned software data to Google name: Do not share scanned software data to Google
recommend: standard recommend: standard
docs: docs:
- https://www.chromium.org/administrators/policy-list-3#ChromeCleanupReportingEnabled - https://www.chromium.org/administrators/policy-list-3#ChromeCleanupReportingEnabled
@@ -2603,6 +2707,8 @@ actions:
children: children:
- -
name: App Connector app name: App Connector app
recommend: strict
docs: https://superuser.com/a/1003226
call: call:
function: UninstallStoreApp function: UninstallStoreApp
parameters: parameters:
@@ -2659,6 +2765,14 @@ actions:
function: UninstallStoreApp function: UninstallStoreApp
parameters: parameters:
packageName: Microsoft.BingFinance packageName: Microsoft.BingFinance
-
name: Uninstall Cortana app
recommend: standard
docs: https://www.microsoft.com/en-us/p/msn-money/9wzdncrfhv4v
call:
function: UninstallStoreApp
parameters:
packageName: Microsoft.549981C3F5F10
- -
name: App Installer app name: App Installer app
docs: https://www.microsoft.com/en-us/p/app-installer/9nblggh4nns1 docs: https://www.microsoft.com/en-us/p/app-installer/9nblggh4nns1
@@ -2921,7 +3035,7 @@ actions:
parameters: parameters:
packageName: Microsoft.ZuneMusic packageName: Microsoft.ZuneMusic
- -
name: Movies & TV app name: Movies and TV app
docs: https://www.microsoft.com/en-us/p/movies-tv/9wzdncrfj3p2 docs: https://www.microsoft.com/en-us/p/movies-tv/9wzdncrfj3p2
call: call:
function: UninstallStoreApp function: UninstallStoreApp
@@ -3197,20 +3311,27 @@ actions:
category: Microsoft Edge category: Microsoft Edge
children: children:
- -
name: Microsoft Edge app name: Microsoft Edge (Legacy) app
recommend: strict recommend: strict
call: call:
function: UninstallSystemApp function: UninstallSystemApp
parameters: parameters:
packageName: Microsoft.MicrosoftEdge packageName: Microsoft.MicrosoftEdge
- -
name: Microsoft Edge Dev Tools Client app name: Microsoft Edge (Legacy) Dev Tools Client app
docs: https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide docs: https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide
recommend: strict recommend: strict
call: call:
function: UninstallSystemApp function: UninstallSystemApp
parameters: parameters:
packageName: Microsoft.MicrosoftEdgeDevToolsClient packageName: Microsoft.MicrosoftEdgeDevToolsClient
-
name: Win32 Web View Host app / Desktop App Web Viewer
recommend: strict
call:
function: UninstallSystemApp
parameters:
packageName: Microsoft.Win32WebViewHost
- -
name: Microsoft PPI Projection app name: Microsoft PPI Projection app
docs: https://en.wikipedia.org/wiki/Perceptive_Pixel docs: https://en.wikipedia.org/wiki/Perceptive_Pixel
@@ -3219,13 +3340,6 @@ actions:
function: UninstallSystemApp function: UninstallSystemApp
parameters: parameters:
packageName: Microsoft.PPIProjection packageName: Microsoft.PPIProjection
-
name: Win32 Web View Host app / Desktop App Web Viewer
recommend: strict
call:
function: UninstallSystemApp
parameters:
packageName: Microsoft.Win32WebViewHost
- -
name: ChxApp app name: ChxApp app
call: call:
@@ -3260,15 +3374,20 @@ actions:
parameters: parameters:
packageName: Microsoft.Windows.ContentDeliveryManager packageName: Microsoft.Windows.ContentDeliveryManager
- -
category: Uninstall Cortana apps category: Uninstall Cortana system apps
children: children:
- -
name: Cortana app (breaks Windows search) name: Search app (breaks Windows search)
docs: https://thegeekpage.com/searchui-exe-suspended-error/ docs: https://thegeekpage.com/searchui-exe-suspended-error/
call: call:
function: UninstallSystemApp -
parameters: function: UninstallSystemApp
packageName: Microsoft.Windows.Cortana parameters:
packageName: Microsoft.Windows.Cortana # Removed since version 2004
-
function: UninstallStoreApp
parameters:
packageName: Microsoft.Windows.Search # Added in version 2004, it was called "Cortana" before now it's plain "Search"
- -
name: Holographic First Run app name: Holographic First Run app
recommend: standard recommend: standard
@@ -3400,15 +3519,17 @@ actions:
children: children:
- -
name: Kill OneDrive process name: Kill OneDrive process
recommend: strict
code: taskkill /f /im OneDrive.exe code: taskkill /f /im OneDrive.exe
revertCode: '"%LOCALAPPDATA%\Microsoft\OneDrive\OneDrive.exe"' revertCode: '"%LOCALAPPDATA%\Microsoft\OneDrive\OneDrive.exe"'
- -
name: Uninstall OneDrive name: Uninstall OneDrive
recommend: strict
code: |- code: |-
if %PROCESSOR_ARCHITECTURE%==x86 ( if %PROCESSOR_ARCHITECTURE%==x86 (
%SystemRoot%\System32\OneDriveSetup.exe /uninstall 2>null %SystemRoot%\System32\OneDriveSetup.exe /uninstall 2>nul
) else ( ) else (
%SystemRoot%\SysWOW64\OneDriveSetup.exe /uninstall 2>null %SystemRoot%\SysWOW64\OneDriveSetup.exe /uninstall 2>nul
) )
revertCode: |- revertCode: |-
if %PROCESSOR_ARCHITECTURE%==x86 ( if %PROCESSOR_ARCHITECTURE%==x86 (
@@ -3418,6 +3539,7 @@ actions:
) )
- -
name: Remove OneDrive leftovers name: Remove OneDrive leftovers
recommend: strict
code: |- code: |-
rd "%UserProfile%\OneDrive" /q /s rd "%UserProfile%\OneDrive" /q /s
rd "%LocalAppData%\Microsoft\OneDrive" /q /s rd "%LocalAppData%\Microsoft\OneDrive" /q /s
@@ -3425,6 +3547,7 @@ actions:
rd "%SystemDrive%\OneDriveTemp" /q /s rd "%SystemDrive%\OneDriveTemp" /q /s
- -
name: Delete OneDrive shortcuts name: Delete OneDrive shortcuts
recommend: strict
docs: https://docs.microsoft.com/en-us/sharepoint/troubleshoot/installation-and-setup/how-to-block-onedrive.exe-from-being-advertised-after-install-office-2016 docs: https://docs.microsoft.com/en-us/sharepoint/troubleshoot/installation-and-setup/how-to-block-onedrive.exe-from-being-advertised-after-install-office-2016
code: |- code: |-
del "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft OneDrive.lnk" /s /f /q del "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft OneDrive.lnk" /s /f /q
@@ -3432,6 +3555,7 @@ actions:
del "%USERPROFILE%\Links\OneDrive.lnk" /s /f /q del "%USERPROFILE%\Links\OneDrive.lnk" /s /f /q
- -
name: Disable usage of OneDrive name: Disable usage of OneDrive
recommend: strict
code: |- code: |-
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OneDrive" /t REG_DWORD /v "DisableFileSyncNGSC" /d 1 /f reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OneDrive" /t REG_DWORD /v "DisableFileSyncNGSC" /d 1 /f
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OneDrive" /t REG_DWORD /v "DisableFileSync" /d 1 /f reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OneDrive" /t REG_DWORD /v "DisableFileSync" /d 1 /f
@@ -3440,6 +3564,7 @@ actions:
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OneDrive" /t REG_DWORD /v "DisableFileSync" /d 0 /f reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\OneDrive" /t REG_DWORD /v "DisableFileSync" /d 0 /f
- -
name: Prevent automatic OneDrive install for current user name: Prevent automatic OneDrive install for current user
recommend: strict
code: reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "OneDriveSetup" /f code: reg delete "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "OneDriveSetup" /f
revertCode: |- revertCode: |-
if %PROCESSOR_ARCHITECTURE%==x86 ( if %PROCESSOR_ARCHITECTURE%==x86 (
@@ -3449,6 +3574,7 @@ actions:
) )
- -
name: Prevent automatic OneDrive install for new users name: Prevent automatic OneDrive install for new users
recommend: strict
code: |- code: |-
reg load "HKU\Default" "%SystemDrive%\Users\Default\NTUSER.DAT" reg load "HKU\Default" "%SystemDrive%\Users\Default\NTUSER.DAT"
reg delete "HKU\Default\software\Microsoft\Windows\CurrentVersion\Run" /v "OneDriveSetup" /f reg delete "HKU\Default\software\Microsoft\Windows\CurrentVersion\Run" /v "OneDriveSetup" /f
@@ -3463,6 +3589,7 @@ actions:
reg unload "HKU\Default" reg unload "HKU\Default"
- -
name: Remove OneDrive from explorer menu name: Remove OneDrive from explorer menu
recommend: strict
code: |- code: |-
reg delete "HKCR\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" /f reg delete "HKCR\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" /f
reg delete "HKCR\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" /f reg delete "HKCR\Wow6432Node\CLSID\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" /f
@@ -3473,11 +3600,23 @@ actions:
reg add "HKCR\Wow6432Node\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" /v System.IsPinnedToNameSpaceTree /d "1" /t REG_DWORD /f reg add "HKCR\Wow6432Node\{018D5C66-4533-4307-9B53-224DE2ED1FE6}" /v System.IsPinnedToNameSpaceTree /d "1" /t REG_DWORD /f
- -
name: Delete all OneDrive related Services name: Delete all OneDrive related Services
recommend: strict
code: for /f "tokens=1 delims=," %%x in ('schtasks /query /fo csv ^| find "OneDrive"') do schtasks /Delete /TN %%x /F code: for /f "tokens=1 delims=," %%x in ('schtasks /query /fo csv ^| find "OneDrive"') do schtasks /Delete /TN %%x /F
- -
name: Delete OneDrive path from registry name: Delete OneDrive path from registry
recommend: strict
docs: https://stackoverflow.com/questions/46744840/export-registry-value-to-file-and-then-set-a-variable-in-batch docs: https://stackoverflow.com/questions/46744840/export-registry-value-to-file-and-then-set-a-variable-in-batch
code: reg delete "HKCU\Environment" /v "OneDrive" /f code: reg delete "HKCU\Environment" /v "OneDrive" /f
-
name: Uninstall Edge (chromium-based)
code:
PowerShell -ExecutionPolicy Unrestricted -Command "
$installer = (Get-ChildItem \"$env:ProgramFiles*\Microsoft\Edge\Application\*\Installer\setup.exe\");
if (!$installer) {
Write-Host Could not find the installer;
} else {
& $installer.FullName -uninstall -system-level -verbose-logging -force-uninstall
}; "
- -
category: Disable built-in Windows features category: Disable built-in Windows features
children: children:
@@ -3603,7 +3742,7 @@ actions:
code: dism /Online /Disable-Feature /FeatureName:"SearchEngine-Client-Package" /NoRestart code: dism /Online /Disable-Feature /FeatureName:"SearchEngine-Client-Package" /NoRestart
revertCode: dism /Online /Enable-Feature /FeatureName:"SearchEngine-Client-Package" /NoRestart revertCode: dism /Online /Enable-Feature /FeatureName:"SearchEngine-Client-Package" /NoRestart
- -
category: Disable capabilities & features on demand category: Uninstall capabilities & features on demand
docs: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-non-language-fod#fods-that-are-not-preinstalled-but-may-need-to-be-preinstalled docs: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-non-language-fod#fods-that-are-not-preinstalled-but-may-need-to-be-preinstalled
children: children:
- -
@@ -3611,237 +3750,343 @@ actions:
children: children:
- -
name: DirectX Configuration Database capability name: DirectX Configuration Database capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "DirectX.Configuration.Database*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"DirectX.Configuration.Database*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: DirectX.Configuration.Database
- -
name: Internet Explorer 11 capability name: Internet Explorer 11 capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Browser.InternetExplorer*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Browser.InternetExplorer*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Browser.InternetExplorer
- -
name: Math Recognizer capability name: Math Recognizer capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "MathRecognizer*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"MathRecognizer*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: MathRecognizer
- -
name: OneSync capability (breaks Mail, People, and Calendar) name: OneSync capability (breaks Mail, People, and Calendar)
recommend: strict recommend: strict
docs: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-non-language-fod#onesync docs: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/features-on-demand-non-language-fod#onesync
code: Powershell -Command "Get-WindowsCapability -Online -Name "OneCoreUAP.OneSync*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"OneCoreUAP.OneSync*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: OneCoreUAP.OneSync
- -
name: OpenSSH client capability name: OpenSSH client capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "OpenSSH.Client*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"OpenSSH.Client*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: OpenSSH.Client
- -
name: PowerShell ISE capability name: PowerShell ISE capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Microsoft.Windows.PowerShell.ISE*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Microsoft.Windows.PowerShell.ISE*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Microsoft.Windows.PowerShell.ISE
- -
name: Print Management Console capability name: Print Management Console capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Print.Management.Console*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Print.Management.Console*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Print.Management.Console
- -
name: Quick Assist capability name: Quick Assist capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "App.Support.QuickAssist*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"App.Support.QuickAssist*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: App.Support.QuickAssist
- -
name: Steps Recorder capability name: Steps Recorder capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "App.StepsRecorder*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"App.StepsRecorder*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: App.StepsRecorder
- -
name: Windows Fax and Scan capability name: Windows Fax and Scan capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Print.Fax.Scan*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Print.Fax.Scan*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Print.Fax.Scan
# Following are excluded because: # Following are excluded because:
# 1. They are not widely considered as "bloatware" as the community # 1. They are not widely considered as "bloatware" as the community
# 2. Do not have known privacy issues # 2. Do not have known privacy issues
# 3. Make Windows more functional when running all scripts # 3. Make Windows more functional when running all scripts
# - # -
# name: WordPad capability # name: WordPad capability
# code: Powershell -Command "Get-WindowsCapability -Online -Name "Microsoft.Windows.WordPad*" | Remove-WindowsCapability -Online" # call:
# revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Microsoft.Windows.WordPad*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" # function: UninstallCapability
# parameters:
# capabilityName: Microsoft.Windows.WordPad
# - # -
# name: Paint capability # name: Paint capability
# code: Powershell -Command "Get-WindowsCapability -Online -Name "Microsoft.Windows.MSPaint*" | Remove-WindowsCapability -Online" # call:
# revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Microsoft.Windows.MSPaint*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" # function: UninstallCapability
# parameters:
# capabilityName: Microsoft.Windows.MSPaint
# - # -
# name: Notepad capability # name: Notepad capability
# code: Powershell -Command "Get-WindowsCapability -Online -Name "Microsoft.Windows.Notepad*" | Remove-WindowsCapability -Online" # call:
# revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Microsoft.Windows.Notepad*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" # function: UninstallCapability
# parameters:
# capabilityName: Microsoft.Windows.Notepad
- -
category: Not preinstalled category: Not preinstalled
children: children:
- -
name: .NET Framework capability name: .NET Framework capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "NetFX3*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"NetFX3*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: NetFX3
- -
name: Mixed Reality capability name: Mixed Reality capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Analog.Holographic.Desktop*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Analog.Holographic.Desktop*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Analog.Holographic.Desktop
- -
name: Wireless Display capability name: Wireless Display capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "App.WirelessDisplay.Connect*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"App.WirelessDisplay.Connect*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: App.WirelessDisplay.Connect
- -
name: Accessibility - Braille Support capability name: Accessibility - Braille Support capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Accessibility.Braille*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Accessibility.Braille*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Accessibility.Braille
- -
name: Developer Mode capability name: Developer Mode capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Tools.DeveloperMode.Core*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Tools.DeveloperMode.Core*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Tools.DeveloperMode.Core
- -
name: Graphics Tools capability name: Graphics Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Tools.Graphics.DirectX*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Tools.Graphics.DirectX*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Tools.Graphics.DirectX
- -
name: IrDA capability name: IrDA capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Network.Irda*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Network.Irda*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Network.Irda
- -
name: Microsoft WebDriver capability name: Microsoft WebDriver capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Microsoft.WebDriver*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Microsoft.WebDriver*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Microsoft.WebDriver
- -
name: MSIX Packaging Tool Driver capability name: MSIX Packaging Tool Driver capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Msix.PackagingTool.Driver*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Msix.PackagingTool.Driver*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Msix.PackagingTool.Driver
- -
category: Networking tools category: Networking tools
children: children:
- -
name: RAS Connection Manager Administration Kit (CMAK) capability name: RAS Connection Manager Administration Kit (CMAK) capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "RasCMAK.Client*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"RasCMAK.Client*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: RasCMAK.Client
- -
name: RIP Listener capability name: RIP Listener capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "RIP.Listener*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"RIP.Listener*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: RIP.Listener
- -
name: Simple Network Management Protocol (SNMP) capability name: Simple Network Management Protocol (SNMP) capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "SNMP.Client*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"SNMP.Client*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: SNMP.Client
- -
name: SNMP WMI Provider capability name: SNMP WMI Provider capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "WMI-SNMP-Provider.Client*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"WMI-SNMP-Provider.Client*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: WMI-SNMP-Provider.Client
- -
name: OpenSSH Server capability name: OpenSSH Server capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "OpenSSH.Server*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"OpenSSH.Server*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: OpenSSH.Server
- -
category: Printing category: Printing
children: children:
- -
name: Enterprise Cloud Print capability name: Enterprise Cloud Print capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Print.EnterpriseCloudPrint*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Print.EnterpriseCloudPrint*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Print.EnterpriseCloudPrint
- -
name: Mopria Cloud Service capability name: Mopria Cloud Service capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Print.MopriaCloudService*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Print.MopriaCloudService*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Print.MopriaCloudService
- -
category: Remote server administration tools (RSAT) category: Remote server administration tools (RSAT)
children: children:
- -
name: Active Directory Domain Services and Lightweight Directory Services Tools capability name: Active Directory Domain Services and Lightweight Directory Services Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.ActiveDirectory.DS-LDS.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.ActiveDirectory.DS-LDS.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.ActiveDirectory.DS-LDS.Tools
- -
name: BitLocker Drive Encryption Administration Utilities capability name: BitLocker Drive Encryption Administration Utilities capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.BitLocker.Recovery.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.BitLocker.Recovery.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.BitLocker.Recovery.Tools
- -
name: Active Directory Certificate Services Tools v name: Active Directory Certificate Services Tools
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.CertificateServices.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.CertificateServices.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.CertificateServices.Tools
- -
name: DHCP Server Tools capability name: DHCP Server Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.DHCP.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.DHCP.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.DHCP.Tools
- -
name: DNS Server Tools capability name: DNS Server Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.Dns.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.Dns.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.Dns.Tools
- -
name: Failover Clustering Tools capability name: Failover Clustering Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.FailoverCluster.Management.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.FailoverCluster.Management.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.FailoverCluster.Management.Tools
- -
name: File Services Tools capability name: File Services Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.FileServices.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.FileServices.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.FileServices.Tools
- -
name: Group Policy Management Tools capability name: Group Policy Management Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.GroupPolicy.Management.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.GroupPolicy.Management.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.GroupPolicy.Management.Tools
- -
name: IP Address Management (IPAM) Client capability name: IP Address Management (IPAM) Client capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.IPAM.Client.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.IPAM.Client.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.IPAM.Client.Tools
- -
name: Data Center Bridging LLDP Tools capability name: Data Center Bridging LLDP Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.LLDP.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.LLDP.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.LLDP.Tools
- -
name: Network Controller Management Tools capability name: Network Controller Management Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.NetworkController.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.NetworkController.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.NetworkController.Tools
- -
name: Network Load Balancing Tools capability name: Network Load Balancing Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.NetworkLoadBalancing.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.NetworkLoadBalancing.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.NetworkLoadBalancing.Tools
- -
name: Remote Access Management Tools capability name: Remote Access Management Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.RemoteAccess.Management.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.RemoteAccess.Management.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.RemoteAccess.Management.Tools
- -
name: Server Manager Tools name: Server Manager Tools
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.ServerManager.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.ServerManager.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.ServerManager.Tools
- -
name: Shielded VM Tools capability name: Shielded VM Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.Shielded.VM.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.Shielded.VM.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.Shielded.VM.Tools
- -
name: Storage Replica Module for Windows PowerShell capability name: Storage Replica Module for Windows PowerShell capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.StorageReplica.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.StorageReplica.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.StorageReplica.Tools
- -
name: Volume Activation Tools capability name: Volume Activation Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.VolumeActivation.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.VolumeActivation.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.VolumeActivation.Tools
- -
name: Windows Server Update Services Tools capability name: Windows Server Update Services Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.WSUS.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.WSUS.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.WSUS.Tools
- -
name: Storage Migration Service Management Tools capability name: Storage Migration Service Management Tools capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.StorageMigrationService.Management.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.StorageMigrationService.Management.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.StorageMigrationService.Management.Tools
- -
name: Systems Insights Module for Windows PowerShell capability name: Systems Insights Module for Windows PowerShell capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Rsat.SystemInsights.Management.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Rsat.SystemInsights.Management.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Rsat.SystemInsights.Management.Tools
- -
category: Storage category: Storage
children: children:
- -
name: Windows Storage Management capability name: Windows Storage Management capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Microsoft.Windows.StorageManagement*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Microsoft.Windows.StorageManagement*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Microsoft.Windows.StorageManagement
- -
name: OneCore Storage Management capability name: OneCore Storage Management capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Microsoft.OneCore.StorageManagement*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Microsoft.OneCore.StorageManagement*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Microsoft.OneCore.StorageManagement
- -
name: Windows Emergency Management Services and Serial Console capability name: Windows Emergency Management Services and Serial Console capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "Windows.Desktop.EMS-SAC.Tools*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"Windows.Desktop.EMS-SAC.Tools*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: Windows.Desktop.EMS-SAC.Tools
- -
name: XPS Viewer capability name: XPS Viewer capability
code: Powershell -Command "Get-WindowsCapability -Online -Name "XPS.Viewer*" | Remove-WindowsCapability -Online" call:
revertCode: Powershell -Command "$capability = Get-WindowsCapability -Online -Name \"XPS.Viewer*\"; Add-WindowsCapability -Name \"$capability.Name\" -Online" function: UninstallCapability
parameters:
capabilityName: XPS.Viewer
- -
category: Advanced settings category: Advanced settings
children: children:
@@ -3921,8 +4166,8 @@ functions:
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\"); $directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
foreach($dir in $directories) { foreach($dir in $directories) {
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; } if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
cmd /c takeown /f \"$dir\" /r /d y | Out-Null; cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
cmd /c icacls \"$dir\" /grant administrators:F /t | Out-Null; cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
$files = Get-ChildItem -File -Path $dir -Recurse -Force; $files = Get-ChildItem -File -Path $dir -Recurse -Force;
foreach($file in $files) { foreach($file in $files) {
if($file.Name.EndsWith('.OLD')) { continue; } if($file.Name.EndsWith('.OLD')) { continue; }
@@ -3930,7 +4175,7 @@ functions:
Write-Host \"Rename '$($file.FullName)' to '$newName'\"; Write-Host \"Rename '$($file.FullName)' to '$newName'\";
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force; Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
} }
}; " };"
revertCode: revertCode:
PowerShell -Command " PowerShell -Command "
$package = (Get-AppxPackage -AllUsers '{{ $packageName }}'); $package = (Get-AppxPackage -AllUsers '{{ $packageName }}');
@@ -3940,12 +4185,38 @@ functions:
$directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\"); $directories = @($package.InstallLocation, \"$env:LOCALAPPDATA\Packages\$($package.PackageFamilyName)\");
foreach($dir in $directories) { foreach($dir in $directories) {
if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; } if ( !$dir -Or !(Test-Path \"$dir\") ) { continue; }
cmd /c takeown /f \"$dir\" /r /d y | Out-Null; cmd /c ('takeown /f \"' + $dir + '\" /r /d y 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
cmd /c icacls \"$dir\" /grant administrators:F /t | Out-Null; cmd /c ('icacls \"' + $dir + '\" /grant administrators:F /t 1> nul'); if($LASTEXITCODE) { throw 'Failed to take ownership'; }
$files = Get-ChildItem -File -Path \"$dir\*.OLD\" -Recurse -Force; $files = Get-ChildItem -File -Path \"$dir\*.OLD\" -Recurse -Force;
foreach($file in $files) { foreach($file in $files) {
$newName = $file.FullName.Substring(0, $file.FullName.Length - 4); $newName = $file.FullName.Substring(0, $file.FullName.Length - 4);
Write-Host \"Rename '$($file.FullName)' to '$newName'\"; Write-Host \"Rename '$($file.FullName)' to '$newName'\";
Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force; Move-Item -LiteralPath \"$($file.FullName)\" -Destination \"$newName\" -Force;
} }
}; " };"
-
name: UninstallCapability
parameters: [ capabilityName ]
code: PowerShell -Command "Get-WindowsCapability -Online -Name '{{ $capabilityName }}*' | Remove-WindowsCapability -Online"
revertCode: PowerShell -Command "$capability = Get-WindowsCapability -Online -Name '{{ $capabilityName }}*'; Add-WindowsCapability -Name \"$capability.Name\" -Online"
-
name: RenameSystemFile
parameters: [ filePath ]
code: |-
if exist "{{ $filePath }}" (
takeown /f "{{ $filePath }}"
icacls "{{ $filePath }}" /grant administrators:F
move "{{ $filePath }}" "{{ $filePath }}.OLD"
echo Moved "{{ $filePath }}" to "{{ $filePath }}.OLD"
) else (
echo No action required: {{ $filePath }} is not found.
)
revertCode: |-
if exist "{{ $filePath }}.OLD" (
takeown /f "{{ $filePath }}.OLD"
icacls "{{ $filePath }}.OLD" /grant administrators:F
move "{{ $filePath }}.OLD" "{{ $filePath }}"
echo Moved "{{ $filePath }}.OLD" to "{{ $filePath }}"
) else (
echo Could not find backup file "{{ $filePath }}.OLD" 1>&2
)

View File

@@ -1,152 +1,47 @@
import { IEntity } from '../infrastructure/Entity/IEntity';
import { ICategory } from './ICategory';
import { IScript } from './IScript';
import { IApplication } from './IApplication'; import { IApplication } from './IApplication';
import { ICategoryCollection } from './ICategoryCollection';
import { IProjectInformation } from './IProjectInformation'; import { IProjectInformation } from './IProjectInformation';
import { RecommendationLevel, RecommendationLevelNames, RecommendationLevels } from './RecommendationLevel'; import { OperatingSystem } from './OperatingSystem';
export class Application implements IApplication { export class Application implements IApplication {
public get totalScripts(): number { return this.queryable.allScripts.length; } constructor(public info: IProjectInformation, public collections: readonly ICategoryCollection[]) {
public get totalCategories(): number { return this.queryable.allCategories.length; } validateInformation(info);
validateCollections(collections);
private readonly queryable: IQueryableApplication;
constructor(
public readonly info: IProjectInformation,
public readonly actions: ReadonlyArray<ICategory>) {
if (!info) {
throw new Error('info is undefined');
}
this.queryable = makeQueryable(actions);
ensureValid(this.queryable);
ensureNoDuplicates(this.queryable.allCategories);
ensureNoDuplicates(this.queryable.allScripts);
} }
public findCategory(categoryId: number): ICategory | undefined { public getSupportedOsList(): OperatingSystem[] {
return this.queryable.allCategories.find((category) => category.id === categoryId); return this.collections.map((collection) => collection.os);
} }
public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] { public getCollection(operatingSystem: OperatingSystem): ICategoryCollection | undefined {
if (isNaN(level)) { return this.collections.find((collection) => collection.os === operatingSystem);
throw new Error('undefined level');
}
if (!(level in RecommendationLevel)) {
throw new Error(`invalid level: ${level}`);
}
return this.queryable.scriptsByLevel.get(level);
}
public findScript(scriptId: string): IScript | undefined {
return this.queryable.allScripts.find((script) => script.id === scriptId);
}
public getAllScripts(): IScript[] {
return this.queryable.allScripts;
}
public getAllCategories(): ICategory[] {
return this.queryable.allCategories;
} }
} }
function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) { function validateInformation(info: IProjectInformation) {
const totalOccurencesById = new Map<TKey, number>(); if (!info) {
for (const entity of entities) { throw new Error('undefined project information');
totalOccurencesById.set(entity.id, (totalOccurencesById.get(entity.id) || 0) + 1);
}
const duplicatedIds = new Array<TKey>();
totalOccurencesById.forEach((index, id) => {
if (index > 1) {
duplicatedIds.push(id);
}
});
if (duplicatedIds.length > 0) {
const duplicatedIdsText = duplicatedIds.map((id) => `"${id}"`).join(',');
throw new Error(
`Duplicate entities are detected with following id(s): ${duplicatedIdsText}`);
} }
} }
interface IQueryableApplication { function validateCollections(collections: readonly ICategoryCollection[]) {
allCategories: ICategory[]; if (!collections) {
allScripts: IScript[]; throw new Error('undefined collections');
scriptsByLevel: Map<RecommendationLevel, readonly IScript[]>; }
} if (collections.length === 0) {
throw new Error('no collection in the list');
function ensureValid(application: IQueryableApplication) { }
ensureValidCategories(application.allCategories); if (collections.filter((c) => !c).length > 0) {
ensureValidScripts(application.allScripts); throw new Error('undefined collection in the list');
} }
const osList = collections.map((c) => c.os);
function ensureValidCategories(allCategories: readonly ICategory[]) { const duplicates = getDuplicates(osList);
if (!allCategories || allCategories.length === 0) { if (duplicates.length > 0) {
throw new Error('Application must consist of at least one category'); throw new Error('multiple collections with same os: ' +
duplicates.map((os) => OperatingSystem[os].toLowerCase()).join('", "'));
} }
} }
function ensureValidScripts(allScripts: readonly IScript[]) { function getDuplicates(list: readonly OperatingSystem[]): OperatingSystem[] {
if (!allScripts || allScripts.length === 0) { return list.filter((os, index) => list.indexOf(os) !== index);
throw new Error('Application must consist of at least one script');
}
for (const level of RecommendationLevels) {
if (allScripts.every((script) => script.level !== level)) {
throw new Error(`none of the scripts are recommended as ${RecommendationLevel[level]}`);
}
}
}
function flattenApplication(categories: ReadonlyArray<ICategory>): [ICategory[], IScript[]] {
const allCategories = new Array<ICategory>();
const allScripts = new Array<IScript>();
flattenCategories(categories, allCategories, allScripts);
return [
allCategories,
allScripts,
];
}
function flattenCategories(
categories: ReadonlyArray<ICategory>,
allCategories: ICategory[],
allScripts: IScript[]): IQueryableApplication {
if (!categories || categories.length === 0) {
return;
}
for (const category of categories) {
allCategories.push(category);
flattenScripts(category.scripts, allScripts);
flattenCategories(category.subCategories, allCategories, allScripts);
}
}
function flattenScripts(
scripts: ReadonlyArray<IScript>,
allScripts: IScript[]): IScript[] {
if (!scripts) {
return;
}
for (const script of scripts) {
allScripts.push(script);
}
}
function makeQueryable(
actions: ReadonlyArray<ICategory>): IQueryableApplication {
const flattened = flattenApplication(actions);
return {
allCategories: flattened[0],
allScripts: flattened[1],
scriptsByLevel: groupByLevel(flattened[1]),
};
}
function groupByLevel(allScripts: readonly IScript[]): Map<RecommendationLevel, readonly IScript[]> {
const map = new Map<RecommendationLevel, readonly IScript[]>();
for (const levelName of RecommendationLevelNames) {
const level = RecommendationLevel[levelName];
const scripts = allScripts.filter((script) => script.level !== undefined && script.level <= level);
map.set(level, scripts);
}
return map;
} }

View File

@@ -0,0 +1,168 @@
import { getEnumNames, getEnumValues } from '@/application/Common/Enum';
import { IEntity } from '../infrastructure/Entity/IEntity';
import { ICategory } from './ICategory';
import { IScript } from './IScript';
import { RecommendationLevel } from './RecommendationLevel';
import { OperatingSystem } from './OperatingSystem';
import { IScriptingDefinition } from './IScriptingDefinition';
import { ICategoryCollection } from './ICategoryCollection';
export class CategoryCollection implements ICategoryCollection {
public get totalScripts(): number { return this.queryable.allScripts.length; }
public get totalCategories(): number { return this.queryable.allCategories.length; }
private readonly queryable: IQueryableCollection;
constructor(
public readonly os: OperatingSystem,
public readonly actions: ReadonlyArray<ICategory>,
public readonly scripting: IScriptingDefinition) {
if (!scripting) {
throw new Error('undefined scripting definition');
}
this.queryable = makeQueryable(actions);
ensureValidOs(os);
ensureValid(this.queryable);
ensureNoDuplicates(this.queryable.allCategories);
ensureNoDuplicates(this.queryable.allScripts);
}
public findCategory(categoryId: number): ICategory | undefined {
return this.queryable.allCategories.find((category) => category.id === categoryId);
}
public getScriptsByLevel(level: RecommendationLevel): readonly IScript[] {
if (isNaN(level)) {
throw new Error('undefined level');
}
if (!(level in RecommendationLevel)) {
throw new Error(`invalid level: ${level}`);
}
return this.queryable.scriptsByLevel.get(level);
}
public findScript(scriptId: string): IScript | undefined {
return this.queryable.allScripts.find((script) => script.id === scriptId);
}
public getAllScripts(): IScript[] {
return this.queryable.allScripts;
}
public getAllCategories(): ICategory[] {
return this.queryable.allCategories;
}
}
function ensureValidOs(os: OperatingSystem): void {
if (os === undefined) {
throw new Error('undefined os');
}
if (os === OperatingSystem.Unknown) {
throw new Error('unknown os');
}
if (!(os in OperatingSystem)) {
throw new Error(`os "${os}" is out of range`);
}
}
function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) {
const totalOccurrencesById = new Map<TKey, number>();
for (const entity of entities) {
totalOccurrencesById.set(entity.id, (totalOccurrencesById.get(entity.id) || 0) + 1);
}
const duplicatedIds = new Array<TKey>();
totalOccurrencesById.forEach((index, id) => {
if (index > 1) {
duplicatedIds.push(id);
}
});
if (duplicatedIds.length > 0) {
const duplicatedIdsText = duplicatedIds.map((id) => `"${id}"`).join(',');
throw new Error(
`Duplicate entities are detected with following id(s): ${duplicatedIdsText}`);
}
}
interface IQueryableCollection {
allCategories: ICategory[];
allScripts: IScript[];
scriptsByLevel: Map<RecommendationLevel, readonly IScript[]>;
}
function ensureValid(application: IQueryableCollection) {
ensureValidCategories(application.allCategories);
ensureValidScripts(application.allScripts);
}
function ensureValidCategories(allCategories: readonly ICategory[]) {
if (!allCategories || allCategories.length === 0) {
throw new Error('must consist of at least one category');
}
}
function ensureValidScripts(allScripts: readonly IScript[]) {
if (!allScripts || allScripts.length === 0) {
throw new Error('must consist of at least one script');
}
for (const level of getEnumValues(RecommendationLevel)) {
if (allScripts.every((script) => script.level !== level)) {
throw new Error(`none of the scripts are recommended as ${RecommendationLevel[level]}`);
}
}
}
function flattenApplication(categories: ReadonlyArray<ICategory>): [ICategory[], IScript[]] {
const allCategories = new Array<ICategory>();
const allScripts = new Array<IScript>();
flattenCategories(categories, allCategories, allScripts);
return [
allCategories,
allScripts,
];
}
function flattenCategories(
categories: ReadonlyArray<ICategory>,
allCategories: ICategory[],
allScripts: IScript[]): IQueryableCollection {
if (!categories || categories.length === 0) {
return;
}
for (const category of categories) {
allCategories.push(category);
flattenScripts(category.scripts, allScripts);
flattenCategories(category.subCategories, allCategories, allScripts);
}
}
function flattenScripts(
scripts: ReadonlyArray<IScript>,
allScripts: IScript[]): IScript[] {
if (!scripts) {
return;
}
for (const script of scripts) {
allScripts.push(script);
}
}
function makeQueryable(
actions: ReadonlyArray<ICategory>): IQueryableCollection {
const flattened = flattenApplication(actions);
return {
allCategories: flattened[0],
allScripts: flattened[1],
scriptsByLevel: groupByLevel(flattened[1]),
};
}
function groupByLevel(allScripts: readonly IScript[]): Map<RecommendationLevel, readonly IScript[]> {
const map = new Map<RecommendationLevel, readonly IScript[]>();
for (const levelName of getEnumNames(RecommendationLevel)) {
const level = RecommendationLevel[levelName];
const scripts = allScripts.filter((script) => script.level !== undefined && script.level <= level);
map.set(level, scripts);
}
return map;
}

View File

@@ -1,20 +1,11 @@
import { IScript } from '@/domain/IScript'; import { ICategoryCollection } from './ICategoryCollection';
import { ICategory } from '@/domain/ICategory';
import { IProjectInformation } from './IProjectInformation'; import { IProjectInformation } from './IProjectInformation';
import { RecommendationLevel } from './RecommendationLevel'; import { OperatingSystem } from './OperatingSystem';
export interface IApplication { export interface IApplication {
readonly info: IProjectInformation; readonly info: IProjectInformation;
readonly totalScripts: number; readonly collections: readonly ICategoryCollection[];
readonly totalCategories: number;
readonly actions: ReadonlyArray<ICategory>;
getScriptsByLevel(level: RecommendationLevel): ReadonlyArray<IScript>; getSupportedOsList(): OperatingSystem[];
findCategory(categoryId: number): ICategory | undefined; getCollection(operatingSystem: OperatingSystem): ICategoryCollection | undefined;
findScript(scriptId: string): IScript | undefined;
getAllScripts(): ReadonlyArray<IScript>;
getAllCategories(): ReadonlyArray<ICategory>;
} }
export { IScript } from '@/domain/IScript';
export { ICategory } from '@/domain/ICategory';

View File

@@ -0,0 +1,19 @@
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory';
export interface ICategoryCollection {
readonly scripting: IScriptingDefinition;
readonly os: OperatingSystem;
readonly totalScripts: number;
readonly totalCategories: number;
readonly actions: ReadonlyArray<ICategory>;
getScriptsByLevel(level: RecommendationLevel): ReadonlyArray<IScript>;
findCategory(categoryId: number): ICategory | undefined;
findScript(scriptId: string): IScript | undefined;
getAllScripts(): ReadonlyArray<IScript>;
getAllCategories(): ReadonlyArray<ICategory>;
}

View File

@@ -0,0 +1,8 @@
import { ScriptingLanguage } from './ScriptingLanguage';
export interface IScriptingDefinition {
readonly fileExtension: string;
readonly language: ScriptingLanguage;
readonly startCode: string;
readonly endCode: string;
}

View File

@@ -2,10 +2,3 @@ export enum RecommendationLevel {
Standard = 0, Standard = 0,
Strict = 1, Strict = 1,
} }
export const RecommendationLevelNames = Object
.values(RecommendationLevel)
.filter((level) => typeof level === 'string') as string[];
export const RecommendationLevels = RecommendationLevelNames
.map((level) => RecommendationLevel[level]) as RecommendationLevel[];

View File

@@ -2,16 +2,16 @@ import { IScriptCode } from './IScriptCode';
export class ScriptCode implements IScriptCode { export class ScriptCode implements IScriptCode {
constructor( constructor(
scriptName: string,
public readonly execute: string, public readonly execute: string,
public readonly revert: string) { public readonly revert: string,
if (!scriptName) { scriptName: string,
throw new Error('script name is undefined'); syntax: ILanguageSyntax) {
} if (!scriptName) { throw new Error('script name is undefined'); }
validateCode(scriptName, execute); if (!syntax) { throw new Error('syntax is undefined'); }
validateCode(scriptName, execute, syntax);
if (revert) { if (revert) {
scriptName = `${scriptName} (revert)`; scriptName = `${scriptName} (revert)`;
validateCode(scriptName, revert); validateCode(scriptName, revert, syntax);
if (execute === revert) { if (execute === revert) {
throw new Error(`${scriptName}: Code itself and its reverting code cannot be the same`); throw new Error(`${scriptName}: Code itself and its reverting code cannot be the same`);
} }
@@ -19,23 +19,28 @@ export class ScriptCode implements IScriptCode {
} }
} }
function validateCode(name: string, code: string): void { export interface ILanguageSyntax {
readonly commentDelimiters: string[];
readonly commonCodeParts: string[];
}
function validateCode(name: string, code: string, syntax: ILanguageSyntax): void {
if (!code || code.length === 0) { if (!code || code.length === 0) {
throw new Error(`code of ${name} is empty or undefined`); throw new Error(`code of ${name} is empty or undefined`);
} }
ensureCodeHasUniqueLines(name, code);
ensureNoEmptyLines(name, code); ensureNoEmptyLines(name, code);
ensureCodeHasUniqueLines(name, code, syntax);
} }
function ensureNoEmptyLines(name: string, code: string): void { function ensureNoEmptyLines(name: string, code: string): void {
if (code.split('\n').some((line) => line.trim().length === 0)) { if (code.split('\n').some((line) => line.trim().length === 0)) {
throw Error(`Script has empty lines "${name}"`); throw Error(`script has empty lines "${name}"`);
} }
} }
function ensureCodeHasUniqueLines(name: string, code: string): void { function ensureCodeHasUniqueLines(name: string, code: string, syntax: ILanguageSyntax): void {
const lines = code.split('\n') const lines = code.split('\n')
.filter((line) => mayBeUniqueLine(line)); .filter((line) => !shouldIgnoreLine(line, syntax));
if (lines.length === 0) { if (lines.length === 0) {
return; return;
} }
@@ -45,13 +50,12 @@ function ensureCodeHasUniqueLines(name: string, code: string): void {
} }
} }
function mayBeUniqueLine(codeLine: string): boolean { function shouldIgnoreLine(codeLine: string, syntax: ILanguageSyntax): boolean {
const trimmed = codeLine.trim(); codeLine = codeLine.toLowerCase();
if (trimmed === ')' || trimmed === '(') { // "(" and ")" are used often in batch code const isCommentLine = () => syntax.commentDelimiters.some((delimiter) => codeLine.startsWith(delimiter));
return false; const consistsOfFrequentCommands = () => {
} const trimmed = codeLine.trim().split(' ');
if (codeLine.startsWith(':: ') || codeLine.startsWith('REM ')) { // Is comment? return trimmed.every((part) => syntax.commonCodeParts.includes(part));
return false; };
} return isCommentLine() || consistsOfFrequentCommands();
return true;
} }

View File

@@ -0,0 +1,32 @@
import { ScriptingLanguage } from './ScriptingLanguage';
import { IScriptingDefinition } from './IScriptingDefinition';
export class ScriptingDefinition implements IScriptingDefinition {
public readonly fileExtension: string;
constructor(
public readonly language: ScriptingLanguage,
public readonly startCode: string,
public readonly endCode: string,
) {
this.fileExtension = findExtension(language);
validateCode(startCode, 'start code');
validateCode(endCode, 'end code');
}
}
function findExtension(language: ScriptingLanguage): string {
switch (language) {
case ScriptingLanguage.shellscript:
return 'sh';
case ScriptingLanguage.batchfile:
return 'bat';
default:
throw new Error(`unsupported language: ${language}`);
}
}
function validateCode(code: string, name: string) {
if (!code) {
throw new Error(`undefined ${name}`);
}
}

View File

@@ -0,0 +1,4 @@
export enum ScriptingLanguage {
batchfile = 0,
shellscript = 1,
}

View File

@@ -1,4 +1,6 @@
import { IEventSubscription } from './ISubscription';
export interface ISignal<T> { export interface ISignal<T> {
on(handler: (data: T) => void): void; on(handler: EventHandler<T>): IEventSubscription;
off(handler: (data: T) => void): void;
} }
export type EventHandler<T> = (data: T) => void;

View File

@@ -0,0 +1,3 @@
export interface IEventSubscription {
unsubscribe(): void;
}

View File

@@ -1,18 +1,28 @@
import { ISignal } from './ISignal'; import { EventHandler, ISignal } from './ISignal';
export { ISignal }; import { IEventSubscription } from './ISubscription';
export class Signal<T> implements ISignal<T> { export class Signal<T> implements ISignal<T> {
private handlers: Array<(data: T) => void> = []; private handlers = new Map<number, EventHandler<T>>();
public on(handler: (data: T) => void): void { public on(handler: EventHandler<T>): IEventSubscription {
this.handlers.push(handler); const id = this.getUniqueEventHandlerId();
} this.handlers.set(id, handler);
return {
public off(handler: (data: T) => void): void { unsubscribe: () => this.handlers.delete(id),
this.handlers = this.handlers.filter((h) => h !== handler); };
} }
public notify(data: T) { public notify(data: T) {
this.handlers.slice(0).forEach((h) => h(data)); for (const handler of Array.from(this.handlers.values())) {
handler(data);
}
}
private getUniqueEventHandlerId(): number {
const id = Math.random();
if (this.handlers.has(id)) {
return this.getUniqueEventHandlerId();
}
return id;
} }
} }

View File

@@ -2,6 +2,7 @@ import fileSaver from 'file-saver';
export enum FileType { export enum FileType {
BatchFile, BatchFile,
ShellScript,
} }
export class SaveFileDialog { export class SaveFileDialog {
public static saveFile(text: string, fileName: string, type: FileType): void { public static saveFile(text: string, fileName: string, type: FileType): void {
@@ -11,7 +12,8 @@ export class SaveFileDialog {
private static readonly mimeTypes = new Map<FileType, string>([ private static readonly mimeTypes = new Map<FileType, string>([
// Some browsers (including firefox + IE) require right mime type // Some browsers (including firefox + IE) require right mime type
// otherwise they ignore extension and save the file as text. // otherwise they ignore extension and save the file as text.
[ FileType.BatchFile, 'application/bat' ], // https://en.wikipedia.org/wiki/Batch_file [ FileType.BatchFile, 'application/bat' ], // https://en.wikipedia.org/wiki/Batch_file
[ FileType.ShellScript, 'text/x-shellscript' ], // https://de.wikipedia.org/wiki/Shellskript#MIME-Typ
]); ]);
private static saveBlob(file: BlobPart, fileType: string, fileName: string): void { private static saveBlob(file: BlobPart, fileType: string, fileName: string): void {

View File

@@ -1,7 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import App from './App.vue'; import App from './App.vue';
import { ApplicationBootstrapper } from './presentation/Bootstrapping/ApplicationBootstrapper'; import { ApplicationBootstrapper } from './presentation/Bootstrapping/ApplicationBootstrapper';
import 'core-js/fn/array/flat-map'; // Here until Vue 3 & CLI v4 https://github.com/vuejs/vue-cli/issues/3834
new ApplicationBootstrapper() new ApplicationBootstrapper()
.bootstrap(Vue); .bootstrap(Vue);

View File

@@ -0,0 +1,55 @@
<template>
<span class="code-wrapper">
<span class="dollar">$</span>
<code><slot></slot></code>
<font-awesome-icon
class="copy-button"
:icon="['fas', 'copy']"
@click="copyCode"
v-tooltip.top-center="'Copy'"
/>
</span>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Clipboard } from '@/infrastructure/Clipboard';
@Component
export default class Code extends Vue {
public copyCode(): void {
const code = this.$slots.default[0].text;
Clipboard.copyText(code);
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
.code-wrapper {
white-space: nowrap;
justify-content: space-between;
font-family: $normal-font;
background-color: $slate;
color: $light-gray;
padding-left: 0.3rem;
padding-right: 0.3rem;
.dollar {
margin-right: 0.5rem;
font-size: 0.8rem;
user-select: none;
}
.copy-button {
margin-left: 1rem;
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
code {
font-size: 1.2rem;
}
}
</style>

View File

@@ -8,11 +8,10 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Emit } from 'vue-property-decorator'; import { Component, Prop, Emit, Vue } from 'vue-property-decorator';
import { StatefulVue } from './StatefulVue';
@Component @Component
export default class IconButton extends StatefulVue { export default class IconButton extends Vue {
@Prop() public text!: number; @Prop() public text!: number;
@Prop() public iconPrefix!: string; @Prop() public iconPrefix!: string;
@Prop() public iconName!: string; @Prop() public iconName!: string;
@@ -21,7 +20,6 @@ export default class IconButton extends StatefulVue {
public onClicked() { public onClicked() {
return; return;
} }
} }
</script> </script>

View File

@@ -0,0 +1,119 @@
<template>
<div class="instructions">
<!-- <p>
Since you're using online version of {{ this.appName }}, you will need to do additional steps after downloading the file to execute your script on macOS:
</p> -->
<p>
<ol>
<li>
<span>Download the file</span>
<font-awesome-icon
class="explanation"
:icon="['fas', 'info-circle']"
v-tooltip.top-center="'You should be prompted to save the script file now, otherwise try to download it again'"
/>
</li>
<li>
<span>Open terminal</span>
<font-awesome-icon
class="explanation"
:icon="['fas', 'info-circle']"
v-tooltip.top-center="'Type Terminal into Spotlight or open from the Applications -> Utilities folder'"
/>
</li>
<li>
<span>Navigate to the folder where you downloaded the file e.g.:</span>
<div>
<Code>cd ~/Downloads</Code>
<font-awesome-icon
class="explanation"
:icon="['fas', 'info-circle']"
v-tooltip.top-center="
'Press on Enter/Return key after running the command.<br/>' +
'If the file is not downloaded on Downloads folder, change `Downloads` to path where the file is downloaded.<br/>' +
' `cd` will change the current folder.<br/>' +
' `~` is the user home directory.'"
/>
</div>
</li>
<li>
<span>Give the file execute permissions:</span>
<div>
<Code>chmod +x {{ this.fileName }}</Code>
<font-awesome-icon
class="explanation"
:icon="['fas', 'info-circle']"
v-tooltip.top-center="
'Press on Enter/Return key after running the command.<br/>' +
'It will make the file executable.'"
/>
</div>
</li>
<li>
<span>Execute the file:</span>
<div>
<Code>./{{ this.fileName }}</Code>
<font-awesome-icon
class="explanation"
:icon="['fas', 'info-circle']"
v-tooltip.top-center="'Alternatively you can double click on the file'"
/>
</div>
</li>
<li>
<span>If asked, enter your administrator password</span>
<font-awesome-icon
class="explanation"
:icon="['fas', 'info-circle']"
v-tooltip.top-center="
'Press on Enter/Return key after typing your password<br/>' +
'Your password will not be shown by default.<br/>' +
'Administor privileges are required to configure OS.'"
/>
</li>
</ol>
</p>
<!-- <p>
Or download the <a :href="this.macOsDownloadUrl">offline version</a> to run your scripts directly to skip these steps.
</p> -->
</div>
</template>
<script lang="ts">
import { Component, Prop } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import Code from './Code.vue';
import { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem';
@Component({
components: {
Code,
},
})
export default class MacOsInstructions extends StatefulVue {
@Prop() public fileName: string;
public appName = '';
public macOsDownloadUrl = '';
protected initialize(app: IApplication): void {
this.appName = app.info.name;
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
}
protected handleCollectionState(): void {
return;
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
li {
margin: 10px 0;
}
.explanation {
margin-left: 0.5em;
}
</style>

View File

@@ -0,0 +1,161 @@
<template>
<div class="container" v-if="hasCode">
<IconButton
:text="this.isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCodeAsync"
icon-prefix="fas"
:icon-name="this.isDesktopVersion ? 'save' : 'file-download'">
</IconButton>
<IconButton
text="Copy"
v-on:click="copyCodeAsync"
icon-prefix="fas" icon-name="copy">
</IconButton>
<modal :name="macOsModalName" height="auto" :scrollable="true" :adaptive="true"
v-if="this.isMacOsCollection">
<div class="modal">
<div class="modal__content">
<MacOsInstructions :fileName="this.fileName" />
</div>
<div class="modal__close-button">
<font-awesome-icon :icon="['fas', 'times']" @click="$modal.hide(macOsModalName)"/>
</div>
</div>
</modal>
</div>
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { SaveFileDialog, FileType } from '@/infrastructure/SaveFileDialog';
import { Clipboard } from '@/infrastructure/Clipboard';
import IconButton from './IconButton.vue';
import MacOsInstructions from './MacOsInstructions.vue';
import { Environment } from '@/application/Environment/Environment';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IApplication } from '@/domain/IApplication';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem';
@Component({
components: {
IconButton,
MacOsInstructions,
},
})
export default class TheCodeButtons extends StatefulVue {
public readonly macOsModalName = 'macos-instructions';
public hasCode = false;
public isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
public isMacOsCollection = false;
public fileName = '';
private codeListener: IEventSubscription;
public async copyCodeAsync() {
const code = await this.getCurrentCodeAsync();
Clipboard.copyText(code.current);
}
public async saveCodeAsync() {
const context = await this.getCurrentContextAsync();
saveCode(this.fileName, context.state);
if (this.isMacOsCollection) {
this.$modal.show(this.macOsModalName);
}
}
public destroyed() {
if (this.codeListener) {
this.codeListener.unsubscribe();
}
}
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
this.fileName = buildFileName(newState.collection.scripting);
this.react(newState.code);
}
private async getCurrentCodeAsync(): Promise<IApplicationCode> {
const context = await this.getCurrentContextAsync();
const code = context.state.code;
return code;
}
private async react(code: IApplicationCode) {
this.hasCode = code.current && code.current.length > 0;
if (this.codeListener) {
this.codeListener.unsubscribe();
}
this.codeListener = code.changed.on((newCode) => {
this.hasCode = newCode && newCode.code.length > 0;
});
}
}
function saveCode(fileName: string, state: ICategoryCollectionState) {
const content = state.code.current;
const type = getType(state.collection.scripting.language);
SaveFileDialog.saveFile(content, fileName, type);
}
function getType(language: ScriptingLanguage) {
switch (language) {
case ScriptingLanguage.batchfile:
return FileType.BatchFile;
case ScriptingLanguage.shellscript:
return FileType.ShellScript;
default:
throw new Error('unknown file type');
}
}
function buildFileName(scripting: IScriptingDefinition) {
const fileName = 'privacy-script';
if (scripting.fileExtension) {
return `${fileName}.${scripting.fileExtension}`;
}
return fileName;
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
.container {
display: flex;
flex-direction: row;
justify-content: center;
}
.container > * + * {
margin-left: 30px;
}
.modal {
font-family: $normal-font;
margin-bottom: 10px;
display: flex;
flex-direction: row;
&__content {
width: 100%;
margin: 5%;
}
&__close-button {
width: auto;
font-size: 1.5em;
margin-right:0.25em;
align-self: flex-start;
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
}
</style>

View File

@@ -21,6 +21,8 @@ import CardListItem from './CardListItem.vue';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { hasDirective } from './NonCollapsingDirective'; import { hasDirective } from './NonCollapsingDirective';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
@Component({ @Component({
components: { components: {
@@ -31,9 +33,7 @@ export default class CardList extends StatefulVue {
public categoryIds: number[] = []; public categoryIds: number[] = [];
public activeCategoryId?: number = null; public activeCategoryId?: number = null;
public async mounted() { public created() {
const state = await this.getCurrentStateAsync();
this.setCategories(state.app.actions);
this.onOutsideOfActiveCardClicked((element) => { this.onOutsideOfActiveCardClicked((element) => {
if (hasDirective(element)) { if (hasDirective(element)) {
return; return;
@@ -41,15 +41,21 @@ export default class CardList extends StatefulVue {
this.activeCategoryId = null; this.activeCategoryId = null;
}); });
} }
public onSelected(categoryId: number, isExpanded: boolean) { public onSelected(categoryId: number, isExpanded: boolean) {
this.activeCategoryId = isExpanded ? categoryId : undefined; this.activeCategoryId = isExpanded ? categoryId : undefined;
} }
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.setCategories(newState.collection.actions);
this.activeCategoryId = undefined;
}
private setCategories(categories: ReadonlyArray<ICategory>): void { private setCategories(categories: ReadonlyArray<ICategory>): void {
this.categoryIds = categories.map((category) => category.id); this.categoryIds = categories.map((category) => category.id);
} }
private onOutsideOfActiveCardClicked(callback: (clickedElement: Element) => void) { private onOutsideOfActiveCardClicked(callback: (clickedElement: Element) => void) {
const outsideClickListener = (event) => { const outsideClickListener = (event) => {
if (!this.activeCategoryId) { if (!this.activeCategoryId) {

View File

@@ -35,6 +35,7 @@
import { Component, Prop, Watch, Emit } from 'vue-property-decorator'; import { Component, Prop, Watch, Emit } from 'vue-property-decorator';
import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue'; import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
@Component({ @Component({
components: { components: {
@@ -49,16 +50,26 @@ export default class CardListItem extends StatefulVue {
public isAnyChildSelected = false; public isAnyChildSelected = false;
public areAllChildrenSelected = false; public areAllChildrenSelected = false;
@Emit('selected') private selectionChangedListener: IEventSubscription;
public async mounted() {
this.updateStateAsync(this.categoryId);
const context = await this.getCurrentContextAsync();
this.selectionChangedListener = context.state.selection.changed.on(() => this.updateStateAsync(this.categoryId));
}
public destroyed() {
if (this.selectionChangedListener) {
this.selectionChangedListener.unsubscribe();
}
}
@Emit('selected')
public onSelected(isExpanded: boolean) { public onSelected(isExpanded: boolean) {
this.isExpanded = isExpanded; this.isExpanded = isExpanded;
} }
@Watch('activeCategoryId') @Watch('activeCategoryId')
public async onActiveCategoryChanged(value: |number) { public async onActiveCategoryChanged(value: |number) {
this.isExpanded = value === this.categoryId; this.isExpanded = value === this.categoryId;
} }
@Watch('isExpanded') @Watch('isExpanded')
public async onExpansionChangedAsync(newValue: number, oldValue: number) { public async onExpansionChangedAsync(newValue: number, oldValue: number) {
if (!oldValue && newValue) { if (!oldValue && newValue) {
@@ -67,22 +78,21 @@ export default class CardListItem extends StatefulVue {
(focusElement as HTMLElement).scrollIntoView({behavior: 'smooth'}); (focusElement as HTMLElement).scrollIntoView({behavior: 'smooth'});
} }
} }
public async mounted() {
const state = await this.getCurrentStateAsync();
state.selection.changed.on(() => {
this.updateStateAsync(this.categoryId);
});
this.updateStateAsync(this.categoryId);
}
@Watch('categoryId') @Watch('categoryId')
public async updateStateAsync(value: |number) { public async updateStateAsync(value: |number) {
const state = await this.getCurrentStateAsync(); const context = await this.getCurrentContextAsync();
const category = !value ? undefined : state.app.findCategory(this.categoryId); const category = !value ? undefined : context.state.collection.findCategory(this.categoryId);
this.cardTitle = category ? category.name : undefined; this.cardTitle = category ? category.name : undefined;
this.isAnyChildSelected = category ? state.selection.isAnySelected(category) : false; const currentSelection = context.state.selection;
this.areAllChildrenSelected = category ? state.selection.areAllSelected(category) : false; this.isAnyChildSelected = category ? currentSelection.isAnySelected(category) : false;
this.areAllChildrenSelected = category ? currentSelection.areAllSelected(category) : false;
}
protected initialize(): void {
return;
}
protected handleCollectionState(): void {
// No need, as categoryId will be updated instead
return;
} }
} }

View File

@@ -15,15 +15,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { Grouping } from './Grouping'; import { Grouping } from './Grouping';
const DefaultGrouping = Grouping.Cards; const DefaultGrouping = Grouping.Cards;
@Component @Component
export default class TheGrouper extends StatefulVue { export default class TheGrouper extends Vue {
public cardsSelected = false; public cardsSelected = false;
public noneSelected = false; public noneSelected = false;
@@ -32,11 +30,9 @@ export default class TheGrouper extends StatefulVue {
public mounted() { public mounted() {
this.changeGrouping(DefaultGrouping); this.changeGrouping(DefaultGrouping);
} }
public groupByCard() { public groupByCard() {
this.changeGrouping(Grouping.Cards); this.changeGrouping(Grouping.Cards);
} }
public groupByNone() { public groupByNone() {
this.changeGrouping(Grouping.None); this.changeGrouping(Grouping.None);
} }

View File

@@ -1,18 +1,18 @@
import { IApplication } from './../../../domain/IApplication';
import { ICategory, IScript } from '@/domain/ICategory'; import { ICategory, IScript } from '@/domain/ICategory';
import { INode, NodeType } from './SelectableTree/Node/INode'; import { INode, NodeType } from './SelectableTree/Node/INode';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
export function parseAllCategories(app: IApplication): INode[] | undefined { export function parseAllCategories(collection: ICategoryCollection): INode[] | undefined {
const nodes = new Array<INode>(); const nodes = new Array<INode>();
for (const category of app.actions) { for (const category of collection.actions) {
const children = parseCategoryRecursively(category); const children = parseCategoryRecursively(category);
nodes.push(convertCategoryToNode(category, children)); nodes.push(convertCategoryToNode(category, children));
} }
return nodes; return nodes;
} }
export function parseSingleCategory(categoryId: number, app: IApplication): INode[] | undefined { export function parseSingleCategory(categoryId: number, collection: ICategoryCollection): INode[] | undefined {
const category = app.findCategory(categoryId); const category = collection.findCategory(categoryId);
if (!category) { if (!category) {
throw new Error(`Category with id ${categoryId} does not exist`); throw new Error(`Category with id ${categoryId} does not exist`);
} }

View File

@@ -15,117 +15,129 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Watch } from 'vue-property-decorator'; import { Component, Prop, Watch } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { ICategory } from '@/domain/ICategory'; import { ICategory } from '@/domain/ICategory';
import { IApplicationState } from '@/application/State/IApplicationState'; import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IFilterResult } from '@/application/State/Filter/IFilterResult'; import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
import { parseAllCategories, parseSingleCategory, getScriptNodeId, getCategoryNodeId, getCategoryId, getScriptId } from './ScriptNodeParser'; import { parseAllCategories, parseSingleCategory, getScriptNodeId,
import SelectableTree from './SelectableTree/SelectableTree.vue'; getCategoryNodeId, getCategoryId, getScriptId } from './ScriptNodeParser';
import { INode, NodeType } from './SelectableTree/Node/INode'; import SelectableTree from './SelectableTree/SelectableTree.vue';
import { SelectedScript } from '@/application/State/Selection/SelectedScript'; import { INode, NodeType } from './SelectableTree/Node/INode';
import { INodeSelectedEvent } from './SelectableTree/INodeSelectedEvent'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { INodeSelectedEvent } from './SelectableTree/INodeSelectedEvent';
import { IApplication } from '@/domain/IApplication';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
@Component({ @Component({
components: { components: {
SelectableTree, SelectableTree,
}, },
}) })
export default class ScriptsTree extends StatefulVue { export default class ScriptsTree extends StatefulVue {
@Prop() public categoryId?: number; @Prop() public categoryId?: number;
public nodes?: ReadonlyArray<INode> = null; public nodes?: ReadonlyArray<INode> = null;
public selectedNodeIds?: ReadonlyArray<string> = []; public selectedNodeIds?: ReadonlyArray<string> = [];
public filterText?: string = null; public filterText?: string = null;
private filtered?: IFilterResult; private filtered?: IFilterResult;
private listeners = new Array<IEventSubscription>();
public async mounted() { public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
const state = await this.getCurrentStateAsync(); const context = await this.getCurrentContextAsync();
// React to state changes switch (event.node.type) {
state.selection.changed.on(this.handleSelectionChanged); case NodeType.Category:
state.filter.filterRemoved.on(this.handleFilterRemoved); toggleCategoryNodeSelection(event, context.state);
state.filter.filtered.on(this.handleFiltered); break;
// Update initial state case NodeType.Script:
await this.initializeNodesAsync(this.categoryId); toggleScriptNodeSelection(event, context.state);
await this.initializeFilter(state.filter.currentFilter); break;
} default:
throw new Error(`Unknown node type: ${event.node.id}`);
public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
const state = await this.getCurrentStateAsync();
switch (event.node.type) {
case NodeType.Category:
toggleCategoryNodeSelection(event, state);
break;
case NodeType.Script:
toggleScriptNodeSelection(event, state);
break;
default:
throw new Error(`Unknown node type: ${event.node.id}`);
}
}
@Watch('categoryId')
public async initializeNodesAsync(categoryId?: number) {
const state = await this.getCurrentStateAsync();
if (categoryId) {
this.nodes = parseSingleCategory(categoryId, state.app);
} else {
this.nodes = parseAllCategories(state.app);
} }
this.selectedNodeIds = state.selection.selectedScripts
.map((selected) => getScriptNodeId(selected.script));
}
public filterPredicate(node: INode): boolean {
return this.filtered.scriptMatches.some(
(script: IScript) => node.id === getScriptNodeId(script))
|| this.filtered.categoryMatches.some(
(category: ICategory) => node.id === getCategoryNodeId(category));
}
private initializeFilter(currentFilter: IFilterResult | undefined) {
if (!currentFilter) {
this.handleFilterRemoved();
} else {
this.handleFiltered(currentFilter);
}
}
private handleSelectionChanged(selectedScripts: ReadonlyArray<SelectedScript>): void {
this.selectedNodeIds = selectedScripts
.map((node) => node.id);
}
private handleFilterRemoved() {
this.filterText = '';
}
private handleFiltered(result: IFilterResult) {
this.filterText = result.query;
this.filtered = result;
}
} }
@Watch('categoryId', { immediate: true })
function toggleCategoryNodeSelection(event: INodeSelectedEvent, state: IApplicationState): void { public async setNodesAsync(categoryId?: number) {
const categoryId = getCategoryId(event.node.id); const context = await this.getCurrentContextAsync();
if (event.isSelected) { if (categoryId) {
state.selection.addOrUpdateAllInCategory(categoryId, false); this.nodes = parseSingleCategory(categoryId, context.state.collection);
} else { } else {
state.selection.removeAllInCategory(categoryId); this.nodes = parseAllCategories(context.state.collection);
}
this.selectedNodeIds = context.state.selection.selectedScripts
.map((selected) => getScriptNodeId(selected.script));
}
public filterPredicate(node: INode): boolean {
return this.filtered.scriptMatches.some(
(script: IScript) => node.id === getScriptNodeId(script))
|| this.filtered.categoryMatches.some(
(category: ICategory) => node.id === getCategoryNodeId(category));
}
public destroyed() {
this.unsubscribeAll();
}
protected initialize(app: IApplication): void {
return;
}
protected async handleCollectionState(newState: ICategoryCollectionState) {
this.setCurrentFilter(newState.filter.currentFilter);
if (!this.categoryId) {
this.nodes = parseAllCategories(newState.collection);
}
this.unsubscribeAll();
this.subscribe(newState);
}
private subscribe(state: ICategoryCollectionState) {
this.listeners.push(state.selection.changed.on(this.handleSelectionChanged));
this.listeners.push(state.filter.filterRemoved.on(this.handleFilterRemoved));
this.listeners.push(state.filter.filtered.on(this.handleFiltered));
}
private unsubscribeAll() {
this.listeners.forEach((listener) => listener.unsubscribe());
this.listeners.splice(0, this.listeners.length);
}
private setCurrentFilter(currentFilter: IFilterResult | undefined) {
if (!currentFilter) {
this.handleFilterRemoved();
} else {
this.handleFiltered(currentFilter);
} }
} }
function toggleScriptNodeSelection(event: INodeSelectedEvent, state: IApplicationState): void { private handleSelectionChanged(selectedScripts: ReadonlyArray<SelectedScript>): void {
const scriptId = getScriptId(event.node.id); this.selectedNodeIds = selectedScripts
const actualToggleState = state.selection.isSelected(scriptId); .map((node) => node.id);
const targetToggleState = event.isSelected;
if (targetToggleState && !actualToggleState) {
state.selection.addSelectedScript(scriptId, false);
} else if (!targetToggleState && actualToggleState) {
state.selection.removeSelectedScript(scriptId);
}
} }
private handleFilterRemoved() {
this.filterText = '';
}
private handleFiltered(result: IFilterResult) {
this.filterText = result.query;
this.filtered = result;
}
}
function toggleCategoryNodeSelection(event: INodeSelectedEvent, state: ICategoryCollectionState): void {
const categoryId = getCategoryId(event.node.id);
if (event.isSelected) {
state.selection.addOrUpdateAllInCategory(categoryId, false);
} else {
state.selection.removeAllInCategory(categoryId);
}
}
function toggleScriptNodeSelection(event: INodeSelectedEvent, state: ICategoryCollectionState): void {
const scriptId = getScriptId(event.node.id);
const actualToggleState = state.selection.isSelected(scriptId);
const targetToggleState = event.isSelected;
if (targetToggleState && !actualToggleState) {
state.selection.addSelectedScript(scriptId, false);
} else if (!targetToggleState && actualToggleState) {
state.selection.removeSelectedScript(scriptId);
}
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@@ -17,8 +17,11 @@
import { IReverter } from './Reverter/IReverter'; import { IReverter } from './Reverter/IReverter';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import { INode } from './INode'; import { INode } from './INode';
import { SelectedScript } from '@/application/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { getReverter } from './Reverter/ReverterFactory'; import { getReverter } from './Reverter/ReverterFactory';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
@Component @Component
export default class RevertToggle extends StatefulVue { export default class RevertToggle extends StatefulVue {
@@ -26,25 +29,30 @@
public isReverted = false; public isReverted = false;
private handler: IReverter; private handler: IReverter;
private selectionChangeListener: IEventSubscription;
public async mounted() { @Watch('node', {immediate: true}) public async onNodeChangedAsync(node: INode) {
await this.onNodeChangedAsync(this.node); const context = await this.getCurrentContextAsync();
const state = await this.getCurrentStateAsync(); this.handler = getReverter(node, context.state.collection);
this.updateState(state.selection.selectedScripts);
state.selection.changed.on((scripts) => this.updateState(scripts));
} }
@Watch('node') public async onNodeChangedAsync(node: INode) {
const state = await this.getCurrentStateAsync();
this.handler = getReverter(node, state.app);
}
public async onRevertToggledAsync() { public async onRevertToggledAsync() {
const state = await this.getCurrentStateAsync(); const context = await this.getCurrentContextAsync();
this.handler.selectWithRevertState(this.isReverted, state.selection); this.handler.selectWithRevertState(this.isReverted, context.state.selection);
} }
private updateState(scripts: ReadonlyArray<SelectedScript>) { protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.updateStatus(newState.selection.selectedScripts);
if (this.selectionChangeListener) {
this.selectionChangeListener.unsubscribe();
}
this.selectionChangeListener = newState.selection.changed.on(
(scripts) => this.updateStatus(scripts));
}
private updateStatus(scripts: ReadonlyArray<SelectedScript>) {
this.isReverted = this.handler.getState(scripts); this.isReverted = this.handler.getState(scripts);
} }
} }

View File

@@ -1,16 +1,16 @@
import { IReverter } from './IReverter'; import { IReverter } from './IReverter';
import { getCategoryId } from '../../../ScriptNodeParser'; import { getCategoryId } from '../../../ScriptNodeParser';
import { SelectedScript } from '@/application/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IApplication } from '@/domain/IApplication';
import { ScriptReverter } from './ScriptReverter'; import { ScriptReverter } from './ScriptReverter';
import { IUserSelection } from '@/application/State/Selection/IUserSelection'; import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
export class CategoryReverter implements IReverter { export class CategoryReverter implements IReverter {
private readonly categoryId: number; private readonly categoryId: number;
private readonly scriptReverters: ReadonlyArray<ScriptReverter>; private readonly scriptReverters: ReadonlyArray<ScriptReverter>;
constructor(nodeId: string, app: IApplication) { constructor(nodeId: string, collection: ICategoryCollection) {
this.categoryId = getCategoryId(nodeId); this.categoryId = getCategoryId(nodeId);
this.scriptReverters = getAllSubScriptReverters(this.categoryId, app); this.scriptReverters = getAllSubScriptReverters(this.categoryId, collection);
} }
public getState(selectedScripts: ReadonlyArray<SelectedScript>): boolean { public getState(selectedScripts: ReadonlyArray<SelectedScript>): boolean {
return this.scriptReverters.every((script) => script.getState(selectedScripts)); return this.scriptReverters.every((script) => script.getState(selectedScripts));
@@ -20,8 +20,8 @@ export class CategoryReverter implements IReverter {
} }
} }
function getAllSubScriptReverters(categoryId: number, app: IApplication) { function getAllSubScriptReverters(categoryId: number, collection: ICategoryCollection) {
const category = app.findCategory(categoryId); const category = collection.findCategory(categoryId);
if (!category) { if (!category) {
throw new Error(`Category with id "${categoryId}" does not exist`); throw new Error(`Category with id "${categoryId}" does not exist`);
} }

View File

@@ -1,5 +1,5 @@
import { SelectedScript } from '@/application/State/Selection/SelectedScript'; import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
import { IUserSelection } from '@/application/State/IApplicationState'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
export interface IReverter { export interface IReverter {
getState(selectedScripts: ReadonlyArray<SelectedScript>): boolean; getState(selectedScripts: ReadonlyArray<SelectedScript>): boolean;

View File

@@ -1,13 +1,13 @@
import { INode, NodeType } from '../INode'; import { INode, NodeType } from '../INode';
import { IReverter } from './IReverter'; import { IReverter } from './IReverter';
import { ScriptReverter } from './ScriptReverter'; import { ScriptReverter } from './ScriptReverter';
import { IApplication } from '@/domain/IApplication';
import { CategoryReverter } from './CategoryReverter'; import { CategoryReverter } from './CategoryReverter';
import { ICategoryCollection } from '@/domain/ICategoryCollection';
export function getReverter(node: INode, app: IApplication): IReverter { export function getReverter(node: INode, collection: ICategoryCollection): IReverter {
switch (node.type) { switch (node.type) {
case NodeType.Category: case NodeType.Category:
return new CategoryReverter(node.id, app); return new CategoryReverter(node.id, collection);
case NodeType.Script: case NodeType.Script:
return new ScriptReverter(node.id); return new ScriptReverter(node.id);
default: default:

View File

@@ -1,7 +1,7 @@
import { IReverter } from './IReverter'; import { IReverter } from './IReverter';
import { getScriptId } from '../../../ScriptNodeParser'; import { getScriptId } from '../../../ScriptNodeParser';
import { SelectedScript } from '@/application/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { IUserSelection } from '@/application/State/IApplicationState'; import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
export class ScriptReverter implements IReverter { export class ScriptReverter implements IReverter {
private readonly scriptId: string; private readonly scriptId: string;

View File

@@ -17,102 +17,121 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'; import { Component, Prop, Vue, Watch } from 'vue-property-decorator';
import LiquorTree from 'liquor-tree'; import LiquorTree from 'liquor-tree';
import Node from './Node/Node.vue'; import Node from './Node/Node.vue';
import { INode } from './Node/INode'; import { INode } from './Node/INode';
import { convertExistingToNode, toNewLiquorTreeNode } from './LiquorTree/NodeWrapper/NodeTranslator'; import { convertExistingToNode, toNewLiquorTreeNode } from './LiquorTree/NodeWrapper/NodeTranslator';
import { INodeSelectedEvent } from './/INodeSelectedEvent'; import { INodeSelectedEvent } from './/INodeSelectedEvent';
import { getNewState } from './LiquorTree/NodeWrapper/NodeStateUpdater'; import { getNewState } from './LiquorTree/NodeWrapper/NodeStateUpdater';
import { LiquorTreeOptions } from './LiquorTree/LiquorTreeOptions'; import { LiquorTreeOptions } from './LiquorTree/LiquorTreeOptions';
import { FilterPredicate, NodePredicateFilter } from './LiquorTree/NodeWrapper/NodePredicateFilter'; import { FilterPredicate, NodePredicateFilter } from './LiquorTree/NodeWrapper/NodePredicateFilter';
import { ILiquorTreeNewNode, ILiquorTreeExistingNode, ILiquorTree, ILiquorTreeNode, ILiquorTreeNodeState } from 'liquor-tree'; import { ILiquorTreeNewNode, ILiquorTreeExistingNode, ILiquorTree, ILiquorTreeNode, ILiquorTreeNodeState } from 'liquor-tree';
/** Wrapper for Liquor Tree, reveals only abstracted INode for communication */ /** Wrapper for Liquor Tree, reveals only abstracted INode for communication */
@Component({ @Component({
components: { components: {
LiquorTree, LiquorTree,
Node, Node,
}, },
}) })
export default class SelectableTree extends Vue { // Keep it stateless to make it easier to switch out export default class SelectableTree extends Vue { // Keep it stateless to make it easier to switch out
@Prop() public filterPredicate?: FilterPredicate; @Prop() public filterPredicate?: FilterPredicate;
@Prop() public filterText?: string; @Prop() public filterText?: string;
@Prop() public selectedNodeIds?: ReadonlyArray<string>; @Prop() public selectedNodeIds?: ReadonlyArray<string>;
@Prop() public initialNodes?: ReadonlyArray<INode>; @Prop() public initialNodes?: ReadonlyArray<INode>;
public initialLiquourTreeNodes?: ILiquorTreeNewNode[] = null; public initialLiquourTreeNodes?: ILiquorTreeNewNode[] = null;
public liquorTreeOptions = new LiquorTreeOptions(new NodePredicateFilter((node) => this.filterPredicate(node))); public liquorTreeOptions = new LiquorTreeOptions(new NodePredicateFilter((node) => this.filterPredicate(node)));
public convertExistingToNode = convertExistingToNode; public convertExistingToNode = convertExistingToNode;
public mounted() { public nodeSelected(node: ILiquorTreeExistingNode) {
if (this.initialNodes) { const event: INodeSelectedEvent = {
const initialNodes = this.initialNodes.map((node) => toNewLiquorTreeNode(node)); node: convertExistingToNode(node),
if (this.selectedNodeIds) { isSelected: node.states.checked,
recurseDown(initialNodes, };
(node) => node.state = updateState(node.state, node, this.selectedNodeIds)); this.$emit('nodeSelected', event);
} return;
this.initialLiquourTreeNodes = initialNodes; }
} else {
throw new Error('Initial nodes are null or empty'); @Watch('initialNodes', { immediate: true })
} public async updateNodesAsync(nodes: readonly INode[]) {
if (this.filterText) { if (!nodes) {
this.updateFilterText(this.filterText); throw new Error('undefined initial nodes');
}
} }
const initialNodes = nodes.map((node) => toNewLiquorTreeNode(node));
public nodeSelected(node: ILiquorTreeExistingNode) { if (this.selectedNodeIds) {
const event: INodeSelectedEvent = { recurseDown(initialNodes,
node: convertExistingToNode(node), (node) => node.state = updateState(node.state, node, this.selectedNodeIds));
isSelected: node.states.checked,
};
this.$emit('nodeSelected', event);
return;
} }
this.initialLiquourTreeNodes = initialNodes;
@Watch('filterText') const api = await this.getLiquorTreeApiAsync();
public updateFilterText(filterText: |string) { api.setModel(this.initialLiquourTreeNodes); // as liquor tree is not reactive to data after initialization
const api = this.getLiquorTreeApi(); }
if (!filterText) { @Watch('filterText', { immediate: true })
api.clearFilter(); public async updateFilterTextAsync(filterText: |string) {
} else { const api = await this.getLiquorTreeApiAsync();
api.filter('filtered'); // text does not matter, it'll trigger the filterPredicate if (!filterText) {
} api.clearFilter();
} } else {
api.filter('filtered'); // text does not matter, it'll trigger the filterPredicate
@Watch('selectedNodeIds')
public setSelectedStatusAsync(selectedNodeIds: ReadonlyArray<string>) {
if (!selectedNodeIds) {
throw new Error('SelectedrecurseDown nodes are undefined');
}
this.getLiquorTreeApi().recurseDown(
(node) => node.states = updateState(node.states, node, selectedNodeIds),
);
}
private getLiquorTreeApi(): ILiquorTree {
if (!this.$refs.treeElement) {
throw new Error('Referenced tree element cannot be found. Probably it\'s not rendered?');
}
return (this.$refs.treeElement as any).tree;
} }
} }
function updateState( @Watch('selectedNodeIds')
old: ILiquorTreeNodeState, public async setSelectedStatusAsync(selectedNodeIds: ReadonlyArray<string>) {
node: ILiquorTreeNode, if (!selectedNodeIds) {
selectedNodeIds: ReadonlyArray<string>): ILiquorTreeNodeState { throw new Error('SelectedrecurseDown nodes are undefined');
return {...old, ...getNewState(node, selectedNodeIds)}; }
const tree = await this.getLiquorTreeApiAsync();
tree.recurseDown(
(node) => node.states = updateState(node.states, node, selectedNodeIds),
);
} }
function recurseDown( private async getLiquorTreeApiAsync(): Promise<ILiquorTree> {
nodes: ReadonlyArray<ILiquorTreeNewNode>, const accessor = (): ILiquorTree => {
handler: (node: ILiquorTreeNewNode) => void) { const uiElement = this.$refs.treeElement;
for (const node of nodes) { return uiElement ? (uiElement as any).tree : undefined;
handler(node); };
if (node.children) { const treeElement = await tryUntilDefinedAsync(accessor, 5, 20); // Wait for it to render
recurseDown(node.children, handler); if (!treeElement) {
} throw Error('Referenced tree element cannot be found. Perhaps it\'s not yet rendered?');
}
return treeElement;
}
}
function updateState(
old: ILiquorTreeNodeState,
node: ILiquorTreeNode,
selectedNodeIds: ReadonlyArray<string>): ILiquorTreeNodeState {
return {...old, ...getNewState(node, selectedNodeIds)};
}
function recurseDown(
nodes: ReadonlyArray<ILiquorTreeNewNode>,
handler: (node: ILiquorTreeNewNode) => void) {
for (const node of nodes) {
handler(node);
if (node.children) {
recurseDown(node.children, handler);
} }
} }
}
async function tryUntilDefinedAsync<T>(
accessor: () => T | undefined,
delayInMs: number, maxTries: number): Promise<T | undefined> {
const sleepAsync = () => new Promise(((resolve) => setTimeout(resolve, delayInMs)));
let triesLeft = maxTries;
let value: T;
while (triesLeft !== 0) {
value = accessor();
if (value) {
return value;
}
triesLeft--;
await sleepAsync();
}
return value;
}
</script> </script>

View File

@@ -6,14 +6,13 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Emit } from 'vue-property-decorator'; import { Component, Prop, Emit, Vue } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { NonCollapsing } from '@/presentation/Scripts/Cards/NonCollapsingDirective'; import { NonCollapsing } from '@/presentation/Scripts/Cards/NonCollapsingDirective';
@Component({ @Component({
directives: { NonCollapsing }, directives: { NonCollapsing },
}) })
export default class SelectableOption extends StatefulVue { export default class SelectableOption extends Vue {
@Prop() public enabled: boolean; @Prop() public enabled: boolean;
@Prop() public label: string; @Prop() public label: string;
@Emit('click') public onClicked() { return; } @Emit('click') public onClicked() { return; }

View File

@@ -7,7 +7,8 @@
label="None" label="None"
:enabled="this.currentSelection == SelectionState.None" :enabled="this.currentSelection == SelectionState.None"
@click="selectAsync(SelectionState.None)" @click="selectAsync(SelectionState.None)"
v-tooltip="'Deselect all selected scripts. Good start to dive deeper into tweaks and select only what you want.'" v-tooltip=" 'Deselect all selected scripts.<br/>' +
'💡 Good start to dive deeper into tweaks and select only what you want.'"
/> />
</div> </div>
<div class="part"> | </div> <div class="part"> | </div>
@@ -16,7 +17,9 @@
label="Standard" label="Standard"
:enabled="this.currentSelection == SelectionState.Standard" :enabled="this.currentSelection == SelectionState.Standard"
@click="selectAsync(SelectionState.Standard)" @click="selectAsync(SelectionState.Standard)"
v-tooltip="'🛡️ Balanced for privacy and functionality. OS and applications will function normally.'" v-tooltip=" '🛡️ Balanced for privacy and functionality.<br/>' +
'OS and applications will function normally.<br/>' +
'💡 Recommended for everyone'"
/> />
</div> </div>
<div class="part"> | </div> <div class="part"> | </div>
@@ -25,7 +28,9 @@
label="Strict" label="Strict"
:enabled="this.currentSelection == SelectionState.Strict" :enabled="this.currentSelection == SelectionState.Strict"
@click="selectAsync(SelectionState.Strict)" @click="selectAsync(SelectionState.Strict)"
v-tooltip="'🚫 Stronger privacy, disables risky functions that may leak your data. Double check selected tweaks!'" v-tooltip=" '🚫 Stronger privacy, disables risky functions that may leak your data.<br/>' +
'⚠️ Double check to remove sripts where you would trade functionality for privacy<br/>' +
'💡 Recommended for daily users that prefers more privacy over non-essential functions'"
/> />
</div> </div>
<div class="part"> | </div> <div class="part"> | </div>
@@ -34,7 +39,9 @@
label="All" label="All"
:enabled="this.currentSelection == SelectionState.All" :enabled="this.currentSelection == SelectionState.All"
@click="selectAsync(SelectionState.All)" @click="selectAsync(SelectionState.All)"
v-tooltip="'🔒 Strongest privacy. Disables any functionality that may leak your data. ⚠️ Not recommended for inexperienced users'" v-tooltip=" '🔒 Strongest privacy, disabling any functionality that may leak your data.<br/>' +
'🛑 Not designed for daily users, it will break important functionalities.<br/>' +
'💡 Only recommended for extreme use-cases like crime labs where no leak is acceptable'"
/> />
</div> </div>
</div> </div>
@@ -45,10 +52,11 @@
import { Component } from 'vue-property-decorator'; import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue'; import { StatefulVue } from '@/presentation/StatefulVue';
import SelectableOption from './SelectableOption.vue'; import SelectableOption from './SelectableOption.vue';
import { IApplicationState } from '@/application/State/IApplicationState';
import { IScript } from '@/domain/IScript'; import { IScript } from '@/domain/IScript';
import { SelectedScript } from '@/application/State/Selection/SelectedScript'; import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
import { RecommendationLevel } from '@/domain/RecommendationLevel'; import { RecommendationLevel } from '@/domain/RecommendationLevel';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
enum SelectionState { enum SelectionState {
Standard, Standard,
@@ -66,56 +74,69 @@ export default class TheSelector extends StatefulVue {
public SelectionState = SelectionState; public SelectionState = SelectionState;
public currentSelection = SelectionState.None; public currentSelection = SelectionState.None;
public async mounted() {
const state = await this.getCurrentStateAsync();
this.updateSelections(state);
state.selection.changed.on(() => {
this.updateSelections(state);
});
}
public async selectAsync(type: SelectionState): Promise<void> { public async selectAsync(type: SelectionState): Promise<void> {
if (this.currentSelection === type) { if (this.currentSelection === type) {
return; return;
} }
const state = await this.getCurrentStateAsync(); const context = await this.getCurrentContextAsync();
selectType(state, type); selectType(context.state, type);
} }
private updateSelections(state: IApplicationState) { protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.updateSelections(newState);
newState.selection.changed.on(() => this.updateSelections(newState));
if (oldState) {
oldState.selection.changed.on(() => this.updateSelections(oldState));
}
}
private updateSelections(state: ICategoryCollectionState) {
this.currentSelection = getCurrentSelectionState(state); this.currentSelection = getCurrentSelectionState(state);
} }
} }
interface ITypeSelector { interface ITypeSelector {
isSelected: (state: IApplicationState) => boolean; isSelected: (state: ICategoryCollectionState) => boolean;
select: (state: IApplicationState) => void; select: (state: ICategoryCollectionState) => void;
} }
const selectors = new Map<SelectionState, ITypeSelector>([ const selectors = new Map<SelectionState, ITypeSelector>([
[SelectionState.None, { [SelectionState.None, {
select: (state) => state.selection.deselectAll(), select: (state) =>
isSelected: (state) => state.selection.totalSelected === 0, state.selection.deselectAll(),
isSelected: (state) =>
state.selection.totalSelected === 0,
}], }],
[SelectionState.Standard, { [SelectionState.Standard, {
select: (state) => state.selection.selectOnly(state.app.getScriptsByLevel(RecommendationLevel.Standard)), select: (state) =>
isSelected: (state) => hasAllSelectedLevelOf(RecommendationLevel.Standard, state), state.selection.selectOnly(
state.collection.getScriptsByLevel(RecommendationLevel.Standard)),
isSelected: (state) =>
hasAllSelectedLevelOf(RecommendationLevel.Standard, state),
}], }],
[SelectionState.Strict, { [SelectionState.Strict, {
select: (state) => state.selection.selectOnly(state.app.getScriptsByLevel(RecommendationLevel.Strict)), select: (state) =>
isSelected: (state) => hasAllSelectedLevelOf(RecommendationLevel.Strict, state), state.selection.selectOnly(state.collection.getScriptsByLevel(RecommendationLevel.Strict)),
isSelected: (state) =>
hasAllSelectedLevelOf(RecommendationLevel.Strict, state),
}], }],
[SelectionState.All, { [SelectionState.All, {
select: (state) => state.selection.selectAll(), select: (state) =>
isSelected: (state) => state.selection.totalSelected === state.app.totalScripts, state.selection.selectAll(),
isSelected: (state) =>
state.selection.totalSelected === state.collection.totalScripts,
}], }],
]); ]);
function selectType(state: IApplicationState, type: SelectionState) { function selectType(state: ICategoryCollectionState, type: SelectionState) {
const selector = selectors.get(type); const selector = selectors.get(type);
selector.select(state); selector.select(state);
} }
function getCurrentSelectionState(state: IApplicationState): SelectionState { function getCurrentSelectionState(state: ICategoryCollectionState): SelectionState {
for (const [type, selector] of Array.from(selectors.entries())) { for (const [type, selector] of Array.from(selectors.entries())) {
if (selector.isSelected(state)) { if (selector.isSelected(state)) {
return type; return type;
@@ -124,8 +145,8 @@ function getCurrentSelectionState(state: IApplicationState): SelectionState {
return SelectionState.Custom; return SelectionState.Custom;
} }
function hasAllSelectedLevelOf(level: RecommendationLevel, state: IApplicationState) { function hasAllSelectedLevelOf(level: RecommendationLevel, state: ICategoryCollectionState) {
const scripts = state.app.getScriptsByLevel(level); const scripts = state.collection.getScriptsByLevel(level);
const selectedScripts = state.selection.selectedScripts; const selectedScripts = state.selection.selectedScripts;
return areAllSelected(scripts, selectedScripts); return areAllSelected(scripts, selectedScripts);
} }

View File

@@ -0,0 +1,74 @@
<template>
<div class="container">
<div v-for="os in this.allOses" :key="os.name">
<span
class="name"
v-bind:class="{ 'current': currentOs === os.os }"
v-on:click="changeOsAsync(os.os)">
{{ os.name }}
</span>
</div>
</div>
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { StatefulVue } from '@/presentation/StatefulVue';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { IApplication } from '@/domain/IApplication';
@Component
export default class TheOsChanger extends StatefulVue {
public allOses: Array<{ name: string, os: OperatingSystem }> = [];
public currentOs: OperatingSystem = undefined;
public async changeOsAsync(newOs: OperatingSystem) {
const context = await this.getCurrentContextAsync();
context.changeContext(newOs);
}
protected initialize(app: IApplication): void {
this.allOses = app.getSupportedOsList()
.map((os) => ({ os, name: renderOsName(os) }));
}
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
this.currentOs = newState.os;
this.$forceUpdate(); // v-bind:class is not updated otherwise
}
}
function renderOsName(os: OperatingSystem): string {
switch (os) {
case OperatingSystem.Windows: return 'Windows';
case OperatingSystem.macOS: return 'macOS (preview)';
default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`);
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/fonts.scss";
@import "@/presentation/styles/colors.scss";
.container {
font-family: $normal-font;
display: flex;
align-items: center;
div + div::before {
content: "|";
margin-left: 0.5rem;
}
.name {
&:not(.current) {
cursor: pointer;
&:hover {
font-weight: bold;
text-decoration: underline;
}
}
&.current {
color: $gray;
}
}
}
</style>

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