Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd41af466f | ||
|
|
970221b996 | ||
|
|
15004ff1f1 | ||
|
|
65226f3984 | ||
|
|
b0a7d0b53b | ||
|
|
ee43fd92a0 | ||
|
|
cf39e6d254 | ||
|
|
1260eea690 | ||
|
|
45a3669443 | ||
|
|
c9b91f6d8f | ||
|
|
9a6b903b92 | ||
|
|
7661575573 | ||
|
|
f1abd7682f | ||
|
|
575636e6b7 | ||
|
|
daa997b21b | ||
|
|
5934b17283 | ||
|
|
d7de420d5c | ||
|
|
df273f7f63 | ||
|
|
67b2d1c11c | ||
|
|
15353d0e25 | ||
|
|
f1e21babbf | ||
|
|
34b8822ac8 | ||
|
|
73e0520de7 | ||
|
|
fbc3b109b9 | ||
|
|
229c13a195 | ||
|
|
d7f9ef1cbe | ||
|
|
7930bef48c | ||
|
|
8b0e47da38 | ||
|
|
2316e3fb68 | ||
|
|
4015e2ccd8 | ||
|
|
cf907d029a | ||
|
|
79a6c8b2ef | ||
|
|
c318bd301a | ||
|
|
86a2b2fda0 | ||
|
|
8a8b7319d5 | ||
|
|
2428de23ee | ||
|
|
7ec889e759 | ||
|
|
9d009c40dd | ||
|
|
663d63bde0 | ||
|
|
6b83dcbf8f | ||
|
|
72e925fb6f | ||
|
|
c299e95bc6 | ||
|
|
2e40605d59 | ||
|
|
3455a2ca6c | ||
|
|
6fe858d86a | ||
|
|
7cc161c828 | ||
|
|
e14bf2bfa0 | ||
|
|
34672414c3 | ||
|
|
f7557bcc0f | ||
|
|
e4b6cdfb18 | ||
|
|
8cd3352017 | ||
|
|
c4ec6a1445 | ||
|
|
b3117c27f2 | ||
|
|
54ba4dbb0b | ||
|
|
a744415eb2 | ||
|
|
55f936fee9 | ||
|
|
d9e44e2574 | ||
|
|
52d4313156 | ||
|
|
c2b531e968 | ||
|
|
ab7d617886 | ||
|
|
b247b12c3f | ||
|
|
c26bc209eb | ||
|
|
ad1872e7cd | ||
|
|
29c7704e0b | ||
|
|
e41e40c5bf | ||
|
|
31e08d231d | ||
|
|
45b8dd972b | ||
|
|
4e72673373 | ||
|
|
92c3dd9232 | ||
|
|
2c5ab3ea7d | ||
|
|
ffa279f3df | ||
|
|
89dddfbb23 |
36
.github/ISSUE_TEMPLATE/1-bug-report-scripts.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/1-bug-report-scripts.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: Bug report (script bug or unexpected script behavior)
|
||||||
|
about: Create a bug report for generated scripts to help privacy.sexy improve
|
||||||
|
labels: bug
|
||||||
|
title: '[BUG]: '
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for reporting an issue with generated script(s).
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
### OS
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Which OS are you using? What version of OS you were using?
|
||||||
|
On Windows you can find it using "Start button" > "Settings" > "System" > "About".
|
||||||
|
On macOS you can find it using "Apple menu (top left corner)" > "About This Mac".
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
|
||||||
|
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
|
||||||
|
<!-- Which scripts did you execute? If applicable, please paste the executed scripts or attach the generated privacy.sexy file . -->
|
||||||
|
|
||||||
|
### Additional information
|
||||||
|
|
||||||
|
<!-- Add any other context about the problem here. -->
|
||||||
52
.github/ISSUE_TEMPLATE/2-bug-report-generic.md
vendored
Normal file
52
.github/ISSUE_TEMPLATE/2-bug-report-generic.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
name: Bug report (unrelated to generated scripts)
|
||||||
|
about: Create a bug report that's not related to generated scripts to help privacy.sexy improve
|
||||||
|
labels: bug
|
||||||
|
title: '[BUG]: '
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for reporting an issue.
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
### To Reproduce
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Expected behavior
|
||||||
|
|
||||||
|
<!--
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Distribution
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, mention how you were using privacy.sexy when the bug was encountered:
|
||||||
|
- Web (on Desktop or mobile?)
|
||||||
|
- Or desktop (Windows, macOS or Linux?)
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Additional context
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Add any other context about the problem here.
|
||||||
|
-->
|
||||||
27
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for privacy.sexy
|
||||||
|
labels: enhancement
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for suggesting an idea to make privacy better. 🤗
|
||||||
|
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Problem Description
|
||||||
|
|
||||||
|
<!-- Please add a clear and concise description of the problem you are seeking to solve with this feature request. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
### Proposed solution
|
||||||
|
|
||||||
|
<!-- Describe the solution you'd like in a clear and concise manner. -->
|
||||||
|
|
||||||
|
### Alternatives considered
|
||||||
|
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
### Additional information
|
||||||
|
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
4
.github/workflows/deploy-desktop.yaml
vendored
4
.github/workflows/deploy-desktop.yaml
vendored
@@ -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
|
||||||
@@ -29,4 +30,5 @@ jobs:
|
|||||||
- name: Publish desktop app
|
- name: Publish desktop app
|
||||||
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
|
||||||
2
.github/workflows/deploy-site.yaml
vendored
2
.github/workflows/deploy-site.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
--secret-access-key ${{secrets.AWS_DEPLOYMENT_USER_SECRET_ACCESS_KEY}} \
|
--secret-access-key ${{secrets.AWS_DEPLOYMENT_USER_SECRET_ACCESS_KEY}} \
|
||||||
--region us-east-1 \
|
--region us-east-1 \
|
||||||
&& \
|
&& \
|
||||||
echo "::set-env name=SESSION_NAME::${{github.actor}}-${{github.event_name}}-$(echo ${{github.sha}} | cut -c1-8)"
|
echo "SESSION_NAME=${{github.actor}}-${{github.event_name}}-$(echo ${{github.sha}} | cut -c1-8)" >> $GITHUB_ENV
|
||||||
working-directory: aws
|
working-directory: aws
|
||||||
-
|
-
|
||||||
name: "Infrastructure: Deploy IAM stack"
|
name: "Infrastructure: Deploy IAM stack"
|
||||||
|
|||||||
1
.github/workflows/security-checks.yaml
vendored
1
.github/workflows/security-checks.yaml
vendored
@@ -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'
|
||||||
|
|
||||||
|
|||||||
408
CHANGELOG.md
408
CHANGELOG.md
@@ -1,183 +1,279 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9.2 (2021-02-13)
|
||||||
|
|
||||||
|
* do not compile with unused locals vuejs/vetur#1063 | [73e0520](https://github.com/undergroundwires/privacy.sexy/commit/73e0520de70cdbaf0ecdc6e9be5e85f003fcfb79)
|
||||||
|
* fix wrong path for NvTelemtry file in NVIDIA script | [34b8822](https://github.com/undergroundwires/privacy.sexy/commit/34b8822ac821acb47e483e21b57e380551bcf455)
|
||||||
|
* refactor event handling to consume base class for lifecycling | [f1e21ba](https://github.com/undergroundwires/privacy.sexy/commit/f1e21babbfaac21903594a37e30163bfe3338279)
|
||||||
|
* make compiler throw if a function call includes an unexpected parameter | [15353d0](https://github.com/undergroundwires/privacy.sexy/commit/15353d0e2513c89ee4ffd9d9c5e9e83ef69b96b6)
|
||||||
|
* refactor vscode configuration scripts using functions #41 | [67b2d1c](https://github.com/undergroundwires/privacy.sexy/commit/67b2d1c11cd5b131dff93a4437db79d96ed8b3dc)
|
||||||
|
* refactor state handling to make application available independent of the state | [df273f7](https://github.com/undergroundwires/privacy.sexy/commit/df273f7f635ab156ac51a8dfb3fec66c4979f1c4)
|
||||||
|
* add test to ensure correct shared functions are being parsed | [d7de420](https://github.com/undergroundwires/privacy.sexy/commit/d7de420d5c91bd9ce64880cd4a4391ad3a0a5401)
|
||||||
|
* refactor and add tests for NonCollapsingDirective | [5934b17](https://github.com/undergroundwires/privacy.sexy/commit/5934b1728328c3b2ece1597b74dd87477d162175)
|
||||||
|
* add GitHub issue templates | [daa997b](https://github.com/undergroundwires/privacy.sexy/commit/daa997b21b624d133c6f5e4cd6b70214588f9144)
|
||||||
|
* correct the typo in application.md (#60) | [575636e](https://github.com/undergroundwires/privacy.sexy/commit/575636e6b728a2bdd1a9bd72c57bbf2752f10887)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.1...0.9.2)
|
||||||
|
|
||||||
|
## 0.9.1 (2021-01-23)
|
||||||
|
|
||||||
|
* in CI/CD, allow publishing to github if release is more than 2 hours old electron-userland/electron-builder#2074 | [cf907d0](https://github.com/undergroundwires/privacy.sexy/commit/cf907d029a6d80682ba78ec887a9c4fab639db51)
|
||||||
|
* in CI/CD, publish packages for other OSes if single one fails | [4015e2c](https://github.com/undergroundwires/privacy.sexy/commit/4015e2ccd8492e0693365b70fbfe3bd0ac7a6ea2)
|
||||||
|
* specify desktop publish targets as defaults (may) change | [2316e3f](https://github.com/undergroundwires/privacy.sexy/commit/2316e3fb6867e5d765eafcf675b77f88bd2a0f52)
|
||||||
|
* fix selection state indicator on cards not showing up | [8b0e47d](https://github.com/undergroundwires/privacy.sexy/commit/8b0e47da38c49cfe2645d7d25970c448ecd200f8)
|
||||||
|
* transpile using babel for legacy browser support | [7930bef](https://github.com/undergroundwires/privacy.sexy/commit/7930bef48c4e9a4fe0823673958ed8377f5ee533)
|
||||||
|
* fix node APIs no longer working on desktop nklayman/vue-cli-plugin-electron-builder#610, nklayman/vue-cli-plugin-electron-builder#742 | [d7f9ef1](https://github.com/undergroundwires/privacy.sexy/commit/d7f9ef1cbebe911aa19f29be8c5fa9360550793e)
|
||||||
|
* improve explanation for selections | [229c13a](https://github.com/undergroundwires/privacy.sexy/commit/229c13a195dee92e4a31731b7b41c319273a16f1)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.0...0.9.1)
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
* 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) | [3785c62](https://github.com/undergroundwires/privacy.sexy/commit/3785c623f837b182d82fa383dfe7709722a67248)
|
||||||
|
* switch places of download and copy buttons | [50fb290](https://github.com/undergroundwires/privacy.sexy/commit/50fb29038ae19b17ec006093db02cf1e568d53c3)
|
||||||
|
* 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 | [1072505](https://github.com/undergroundwires/privacy.sexy/commit/1072505219edc47d82a91f148d1f310f32869fea)
|
||||||
|
* 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 | [d0019c2](https://github.com/undergroundwires/privacy.sexy/commit/d0019c2c9b1eea620e2e8e02b586903ce62b80e3)
|
||||||
|
* 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 | [1a9db31](https://github.com/undergroundwires/privacy.sexy/commit/1a9db31c7778c3269a71c0bd9665827efda70a02)
|
||||||
|
* add support for shared functions #41 | [8ce06fa](https://github.com/undergroundwires/privacy.sexy/commit/8ce06facbd54184402a4b1af3c7303e64db85b8a)
|
||||||
|
* hide scrollbars on code area when not overflowing | [fd28eaa](https://github.com/undergroundwires/privacy.sexy/commit/fd28eaad061c75ea1aa7e0f0d60ea37a7e52f8c4)
|
||||||
|
* 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)
|
||||||
|
|
||||||
## 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)
|
||||||
|
|
||||||
@@ -187,91 +283,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)
|
||||||
|
|
||||||
|
|||||||
@@ -23,17 +23,6 @@
|
|||||||
- ❗ DON'T
|
- ❗ DON'T
|
||||||
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
|
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
|
||||||
|
|
||||||
## Guidelines
|
|
||||||
|
|
||||||
### Handle the state in presentation layer
|
|
||||||
|
|
||||||
- There are two types of components:
|
|
||||||
- **Stateless**, extends `Vue`
|
|
||||||
- **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
|
|
||||||
- They mutate or/and reacts to changes in [application state](src/application/State/ApplicationState.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.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.
|
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -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 🍑🍆
|
||||||
|
|
||||||
[](./CONTRIBUTING.md)
|
[](./CONTRIBUTING.md)
|
||||||
[](https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript)
|
[](https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript)
|
||||||
@@ -14,11 +14,13 @@
|
|||||||
|
|
||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- Online version: [https://privacy.sexy](https://privacy.sexy)
|
- Online version at [https://privacy.sexy](https://privacy.sexy)
|
||||||
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.6/privacy.sexy-Setup-0.7.6.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.6/privacy.sexy-0.7.6.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.7.6/privacy.sexy-0.7.6.dmg)
|
- 💡 No need to run any compiled software on your computer.
|
||||||
- 💡 Come back regularly to apply latest version for stronger privacy and security.
|
- Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.2/privacy.sexy-Setup-0.9.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.2/privacy.sexy-0.9.2.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.2/privacy.sexy-0.9.2.AppImage).
|
||||||
|
- 💡 Single click to execute your script.
|
||||||
|
- ❗ Come back regularly to apply latest version for stronger privacy and security.
|
||||||
|
|
||||||
[](https://privacy.sexy)
|
[](https://privacy.sexy)
|
||||||
|
|
||||||
## Why
|
## Why
|
||||||
|
|
||||||
@@ -32,9 +34,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,25 +53,17 @@
|
|||||||
- 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.7.6 .`
|
1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.2 .`
|
||||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.7.6 undergroundwires/privacy.sexy:0.7.6`
|
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.2 undergroundwires/privacy.sexy:0.9.2`
|
||||||
|
|
||||||
## Architecture
|
## Architecture overview
|
||||||
|
|
||||||
### Application
|
### Application
|
||||||
|
|
||||||
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪
|
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪
|
||||||
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
|
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
|
||||||
- Application uses highly decoupled models & services in different DDD layers.
|
- Application uses highly decoupled models & services in different DDD layers.
|
||||||
- **Domain layer** is where the application is modelled with validation logic.
|
- 📖 Read more on • [Presentation](./docs/presentation.md) • [Application](./docs/application.md)
|
||||||
- **Presentation Layer**
|
|
||||||
- Consists of Vue.js components and other UI-related code.
|
|
||||||
- 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.
|
|
||||||
- **Application Layer**
|
|
||||||
- Keeps the application state
|
|
||||||
- The [state](src/application/State/ApplicationState.ts) is a mutable singleton & event producer.
|
|
||||||
- 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))
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@vue/cli-plugin-babel/preset'
|
||||||
|
]
|
||||||
|
}
|
||||||
22
docs/application.md
Normal file
22
docs/application.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Application
|
||||||
|
|
||||||
|
- It's mainly responsible for
|
||||||
|
- creating and event based [application state](#application-state)
|
||||||
|
- parsing and compiling [application data](#application-data)
|
||||||
|
|
||||||
|
## Application state
|
||||||
|
|
||||||
|
- [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) holds the [CategoryCollectionState](./../src/application/Context/State/CategoryCollectionState.ts) for each OS
|
||||||
|
- Uses [state pattern](https://en.wikipedia.org/wiki/State_pattern)
|
||||||
|
- Same instance is shared throughout the application to ensure consistent state
|
||||||
|
- 📖 See [Application State | Presentation layer](./presentation.md#application-state) to read more about how the state should be managed by the presentation layer.
|
||||||
|
- 📖 See [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) to start diving into the state code.
|
||||||
|
|
||||||
|
## Application data
|
||||||
|
|
||||||
|
- Compiled to `Application` domain object.
|
||||||
|
- The scripts are defined and controlled in different data files per OS
|
||||||
|
- Enables [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming) and easier contributions
|
||||||
|
- Application data is defined in collection files and
|
||||||
|
- 📖 See [Application data | Presentation layer](./presentation.md#application-data) to read how the application data is read by the presentation layer.
|
||||||
|
- 📖 See [collection files documentation](./collection-files.md) to read more about how the data files are structured/defined and see [collection yaml files](./../src/application/collections/) to directly check the code.
|
||||||
@@ -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.
|
||||||
@@ -94,11 +101,15 @@
|
|||||||
### `Function`
|
### `Function`
|
||||||
|
|
||||||
- Functions allow re-usable code throughout the defined scripts.
|
- Functions allow re-usable code throughout the defined scripts.
|
||||||
- Functions are templates compiled by privacy.sexy and uses special expressions.
|
- Functions are templates compiled by privacy.sexy and uses special [expressions](#expressions).
|
||||||
- Expressions are defined inside mustaches (double brackets, `{{` and `}}`)
|
- Functions can call other functions by defining `call` property instead of `code`
|
||||||
- 👀 See [parameter substitution](#parameter-substitution) for an example usage
|
- 👀 See [parameter substitution](#parameter-substitution) for an example usage
|
||||||
|
|
||||||
#### Parameter substitution
|
#### Expressions
|
||||||
|
|
||||||
|
- Expressions are defined inside mustaches (double brackets, `{{` and `}}`)
|
||||||
|
|
||||||
|
##### Parameter substitution
|
||||||
|
|
||||||
A simple function example
|
A simple function example
|
||||||
|
|
||||||
@@ -118,6 +129,22 @@ It would print "Hello world" if it's called in a [script](#script) as following:
|
|||||||
argument: World
|
argument: World
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A function can call other functions such as:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
-
|
||||||
|
function: CallerFunction
|
||||||
|
parameters: [ 'value' ]
|
||||||
|
call:
|
||||||
|
function: EchoArgument
|
||||||
|
parameters:
|
||||||
|
argument: {{ $value }}
|
||||||
|
-
|
||||||
|
function: EchoArgument
|
||||||
|
parameters: [ 'argument' ]
|
||||||
|
code: Hello {{ $argument }} !
|
||||||
|
```
|
||||||
|
|
||||||
#### `Function` syntax
|
#### `Function` syntax
|
||||||
|
|
||||||
- `name`: *`string`* (**required**)
|
- `name`: *`string`* (**required**)
|
||||||
@@ -127,13 +154,33 @@ 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](#expressions)
|
||||||
- ❗ Parameter names must be unique
|
- ❗ Parameter names must be unique
|
||||||
`code`: *`string`* (**required**)
|
`code`: *`string`* (**required** if `call` is undefined)
|
||||||
- Batch file commands that will be executed
|
- Batch file commands that will be executed
|
||||||
- 💡 If defined, best practice to also define `revertCode`
|
- 💡 If defined, best practice to also define `revertCode`
|
||||||
|
- ❗ If not defined `call` must be defined
|
||||||
- `revertCode`: *`string`*
|
- `revertCode`: *`string`*
|
||||||
- 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`
|
||||||
|
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
|
||||||
|
- A shared function or sequence of functions to call (called in order)
|
||||||
|
- The parameter values that are sent can use [expressions](#expressions)
|
||||||
|
- ❗ If not defined `code` must be defined
|
||||||
|
|
||||||
|
### `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 }}!`
|
||||||
24
docs/presentation.md
Normal file
24
docs/presentation.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Presentation layer
|
||||||
|
|
||||||
|
- Consists of Vue.js components and other UI-related code.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
## Application data
|
||||||
|
|
||||||
|
- Components and should use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain.
|
||||||
|
- [Application.ts](../src/domain/Application.ts) domain model is the stateless application representation including
|
||||||
|
- available scripts, collections as defined in [collection files](./collection-files.md)
|
||||||
|
- package information as defined in [`package.json`](./../package.json)
|
||||||
|
- 📖 See [Application data | Application layer](./presentation.md#application-data) where application data is parsed and compiled.
|
||||||
|
|
||||||
|
## Application state
|
||||||
|
|
||||||
|
- Stateful components mutate or/and react to state changes in [ApplicationContext](./../src/application/Context/ApplicationContext.ts).
|
||||||
|
- Stateless components that does not handle state extends `Vue`
|
||||||
|
- Stateful components that depends on the collection state such as user selection, search queries and more extends [`StatefulVue`](./../src/presentation/StatefulVue.ts)
|
||||||
|
- The single source of truth is a singleton of the state created and made available to presentation layer by [`StatefulVue`](./../src/presentation/StatefulVue.ts)
|
||||||
|
- `StatefulVue` includes abstract `handleCollectionState` that is fired once the component is loaded and also each time [collection](./collection-files.md) is changed.
|
||||||
|
- Do not forget to subscribe from events when component is destroyed or if needed [collection](./collection-files.md) is changed.
|
||||||
|
- 💡 `events` in base class [`StatefulVue`](./../src/presentation/StatefulVue.ts) makes lifecycling easier
|
||||||
|
- 📖 See [Application state | Application layer](./presentation.md#application-state) where the state is implemented using using state pattern.
|
||||||
29
docs/tests.md
Normal file
29
docs/tests.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Unit tests
|
||||||
|
|
||||||
|
- Unit tests are defined in [`./tests`](./../tests)
|
||||||
|
- They follow same folder structure as [`./src`](./../src)
|
||||||
|
|
||||||
|
## Naming
|
||||||
|
|
||||||
|
- Each test suite first describe the system under test
|
||||||
|
- E.g. tests for class `Application` is categorized under `Application`
|
||||||
|
- Tests for specific methods are categorized under method name (if applicable)
|
||||||
|
- E.g. test for `run()` is categorized under `run`
|
||||||
|
|
||||||
|
## Act, arrange, assert
|
||||||
|
|
||||||
|
- Tests use act, arrange and assert (AAA) pattern when applicable
|
||||||
|
- **Arrange**
|
||||||
|
- Should set up the test case
|
||||||
|
- Starts with comment line `// arrange`
|
||||||
|
- **Act**
|
||||||
|
- Should cover the main thing to be tested
|
||||||
|
- Starts with comment line `// act`
|
||||||
|
- **Assert**
|
||||||
|
- Should elicit some sort of response
|
||||||
|
- Starts with comment line `// assert`
|
||||||
|
|
||||||
|
## Stubs
|
||||||
|
|
||||||
|
- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs)
|
||||||
|
- They implement dummy behavior to be functional
|
||||||
@@ -1 +1 @@
|
|||||||
<mxfile host="www.draw.io" modified="2019-12-27T03:04:27.829Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" etag="O-1eaon4mqUmgvki0auB" version="12.4.3" pages="1"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3Zpdk6I4FIZ/jZdakPDlpa3tzlTNbnVVV83OzE1XhACZAUKF2Or8+k0wCBhau1f8GrpayUkC5HlzTg7IAE7T9V8M5fHfNMDJABjBegBnAwA8YIpPadhsDS50t4aIkWBrMmvDM/mNldFQ1iUJcNFqyClNOMnbRp9mGfZ5y4YYo6t2s5Am7bPmKMKa4dlHiW79lwQ8VsOyjdr+CZMors5sGqomRVVjZShiFNBVwwQfB3DKKOXbvXQ9xYlkV3HZ9pu/Ubu7MIYz/p4OD0/efJxMXmZfs6kd/vj2Txp+HlrVxfFNNWIcCACqSBmPaUQzlDzW1gdGl1mA5WENUarbfKE0F0ZTGH9izjdKTbTkVJhiniaqFq8J/ya7j2xV+t6oma3VkcvCpipknG0anWTxe7Ou7laWqn4FZ/QXntKEsnJ8cFxuomY7cjncN4kqU0GXzMcHMFYzE7EI8wPtwE534S+YplhcqejHcII4eW1fB1IzN9q1q8UVO0rfj2gNrqL1TreWarWI96AbPFG3suuEMbRpNMgpyXjROPKTNIgGKnZC5Zsqctp77n24NTDtvQmzPX09fXbjOGFGwatGj0bsMEbAfmf4uONpaF0zfKjjvqJkqc70xHAhxirOTTNtIrRlXsWE4+cclRhWIlNoSxqSJGkwDmzsBVYXfQ8soOPIHjTj1TyrrgwzjteHxdDhqQ6O23Yf5U2res2H1coeN9Z7xzgTbehc07XMlmu907PMtmeBm3AtcCHX+n8RftwO2iZsBe2j7YHrnj/IQ+9eUsSbmlCnpgwnRQ+gxepJnifE7yNUtwKvFrpDW/51ieGUmzpCw77ddiKdFMXtdhTf3Yw1wzjoCOPu2cK4fS/Oc9P3V/AeEiSoOd2Mpoj0mxotzCAIO7mbhgvH+Ayp0XjPqca6UwGrw6nsczmVpYH+nIUMCSJLny8Z7hV46PnY97uALzzbso0zAHf3clGnA7h3ySjmaMDxK1aZzRugzY+DDrHTDTpwxwvD6GeF8PbYujpbtwMtOBdaV0PLcE4Lwqk6+F3jhR0L8EXxenqoeMw44ffIdj8uXB3uWIPLY4ZRQLLoD8B79dBg6ll8Kn9e6TfsLpDvBbALLoCWZQf9wBXJSRuuYelw7ZHZgRecC6+er4ldEhy7R7pVxMA2R+Obg2xrkAuO+KEU7R18e6C15+xm9ZTjiqjOk2X1MbOc47DMrqej52Olp02o+YDDSGhE/NsDZ+pLymXBVWPoBjfaiNFfG9rtMdMXCg1SEaNc7pK0fJ+giUTCEHyTSUKiTNi4fIizs35BC5w8yYRfTlw4W1DOaSoaJLLiAfm/olKAVjokN9GkPNmkyLfvPcg0CFWFkKylZA/qemYx5/KFiYkkAeZ+kFkj4tMsJEJaNvLFGcFcrHxIfEm7iDlz4UK0GKIsGC6Y+JQmW6Ykc+i4L1+X+GfxIpuINMUb5VXG1+t9r60Jb1/yMQPQ16771x1+WPchCugC79QHnvfyjIqiR91NcGPC6yvx/QtvHhV+ka3Ep5B1Lf7FnlgYpFk+3Sp6Uhq4+zkqBCNdbQh1tStb/2rrucT9q20cVTukLMLZsMBU/rI/d8Stwjwn5fPTYkhzTlLyu0wKenR0WN0O7MTXpbc6pLc+LL0o1m/WbX8JrV9PhI//AQ==</diagram></mxfile>
|
<mxfile host="Electron" modified="2021-01-31T12:32:01.751Z" agent="5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.88 Electron/11.1.1 Safari/537.36" etag="OTbSPW1ZOLwiPL6mt-j9" version="14.1.8" type="device"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3VtZd6JKF/01/dhZjA6PREjCXRbGFpM2L70QCYIoLsUI/PpvnwKcMD3cazrJl6wEqfHU3vvsKoz5Infm6e3KWU5ZPPGiL5IwSb/I+hdJUsUmflNBVhQoDaEo8FfBpCgS9wWDIPfKwqrZJph466OGSRxHSbA8LnTjxcJzk6MyZ7WKt8fNnuPoeNal43u1goHrRPXSx2CSTIvSlirsy++8wJ9WM4tCWTN3qsZlwXrqTOLtQZFsfJE7qzhOilfztONFhF2FS9Hv5pXaXWArb5H8Tofr+9ZNO9J+6A+Ljvr89N2aP5tflSq4JKtW7E0AQHkbr5Jp7McLJzL2pdereLOYeDSsgLt9m24cL1EoojD0kiQr2XQ2SYyiaTKPylovDZLv1P1KLe9GBzV6Wo7Mb7LqZpGssoNOdDs6rNt343dVv3WyimdeJ47iFV+f3OZfqKkDWGK6jjcr1/sJapUQnZXvJT9pJxXtCNGDCUp6br147iFSNFh5kZMEL8eSc0rl+rt2e3LxouT3T7iW3oXrHW9HrO1J/IC8yZfmjXfVVisnO2iwjINFsj4Y+Z4K0KD0TrnMzdI51ZP0/nlrSVRPBFNMv5fPbh3/QVHyu7rHgXcIV5L6m/bxeWSofCj7KMd9caJNOdP9yltj8Zg7XtSEcEzzdhok3mDpcFy2OCkcU/ocRNEBxhPVa02Uc+i3pLHcaFCPeJFUOjuzFVbBeqvES38KXlnbaB6nT5lN2/2eL1c7+/Rgv69OMxdHW268Z2qJR6n1m5klHmeW9B6pJb1Xav07h28fm7YoH5n2L9tLzebbm7zc+ixHxPcU1MWPDP/JPaSaV2vLZRS4l7DqI+OtWfezSt/nyGjwr3KEg/Li62ck/b6Lq8cuvnsYO7Rx6YyNN9/MxtXPkjwf6flK/pQHJLmWdHo8d4LLHo3G4mTyfBZ3UWjKbe8Njkbtk6Rq15NKUs4klfpWSaXUgDYXzysHiGzcZLPyLgr4c8v1XPcc4OOWqqjCGwDePDmLNs4A3vqbLtaoAe69eOXJ5hWgxT8H+tlrnAd60myPBeEyO0TrBNtmHdvmGWilt4K2WYN25S3jdZDE5eCfGl75zAb8V+Ft1a3CWCRB8hmxPfWFdwe3XQM3ma48ZxIs/P8DeN/dGsT6KX5Of165rO2OBU/2GufAFbyW0GpdBlwcTo7BFZQ6uOqVeAZe6a3grZ/X8DKY/OoZ6aNCLKniVfvDgazWQF4nTvKzI9qf42vo9H0ZFE9MQKze/XhHCP/K6euSGJ6+mXoORPHcu6lvh2H9mOUcviEiRLEfuJ8HULG+Nf1dQHdr2AOazamXG0eR5xKqH1efHw/N+lZUA289dZb0MpjzTywcQkUrh5QjLQr8BcoSeptoV9p1xl50T48UJHVZH8dJEs/RIKKKa8ed+ZyYowMXfaEJn0xbL4tPVtBBy6lunoOUqLwu49GnSUIfydAICenGnSyUq8CNF88BKF9duZhRusHe6uBC5VDHDZIuXn91FpOv4xV+U5FKh54budH88bDxwvUPaoKDUOtqWZ0pL/pk3aa/UpxyryhXap38fenl6a9vkp+ffvmP6f/qTOKxtxOB1Gr9GDjr9QXpl9TWVVP9PQlI5zUgvZkI6tv85xeB+EsRjBdb/AbFKX7wCtsyFdNbausLsS7LyhHlcuOqVT+QSO0zht9+K7Lr55HPT7bwS7Kf45XvLb6uvZg+TXDTAC83y4C/Z7v+Gi+TYB7k/ER2wZzfvUtSsS/XqK/0cUh9VfZfqH/Y3Hrj5vegr7fv2awhLwLh8cyf5n7J/CWIPS+Z8+Qe8V/RzamUteJWulm/gJzrlM58nfs7S3rKrpXxY7pxcyFw7r4Jrh6/dOWJPMlUmWXqizt3X1iobVmnnU/mbmDeTZZPd9/i+4GZWfYoMG+nkfM4iSe6ELBwKJnBteQ8Psj9eVtBm62paz4v103RCkzMfX/r+k/zaD1Gj/G8vXkamMV9R8wmj2l0P/gnmswfNmPp28wMldZIirKRlEbm7dNyfLttmwHL+uE/N0wwMLtFs6Qs9Om1unt9Z7bN0Mh6HfPlPkwXB33Vb7OH62/hLCjLEnfxsH6yi1i8+UM2zniUd9fTya3vPyFK2zawfkVkuulbubGxwijs2qbUBS7WQBC6oZtbgSBYgSJaobthoZl27SGVSyzQUqujCMweJqhPcfWZrW0se5Z17f6G5SMZ2KDtKLcGGq6mYOpMxb1gzY0MV8nKtOoKPA2fdajdKMOcVC4yiSVWpsiWbSbMZhjbxNgzxDFCHKZIYzEepylYmZAjnhx1PuJRmO6jD9aUz1LiqBsaQq8jpKjLMJZv6RjH9uWubaTd0M+tx3MxaRTTpmcPJawb8/Zlby4kWBvKZqqpu2jLtpbEUo4jxwBz2kOxWLshW4GWsUDJe3bfZ/kM8ZBmGOZkSq+j5bQ+FjJoiWL1wYO76el+jv4y4tiygaaijYR+GBu42po0sk1wxfEtr4hzoG27wIjpmkj4WzraZVgvjU9zow/TXQXrABY+8Qeeh4SJivi2TGcJHz830MYgvMCvoIBrzkcP2Fq8DeFPPDDU9WX0zxnVvcqpkHfDmQAN4DpUvYArdzv6/i02b/ttcyakPYo3NGl9W8xHvFPcWyg+QY5izQblGcadIXcFgc+n+xwP8CsWmhymLBBIu+grEB8CcEW8Ptqwck0zWgv0CDz1IepGGLt/Hs8O8BwoWyuc+Wifs9wKGdeARtio4C4j3Vvh0IfuVHALfg1gPEOsBtbaV0x9RPFluE8pZ4ABrn3oxkQe+FuKE30zWgt+gP1MfY1bU+8Tf9Ay8kDvS6RDi17nlAsjhWvtkdY7pHFIY4h1poInmluxOhrmdIUe8gXYiozj7UNrTCy4nAms0KqAssQKSQt96Jj0MiIfkICpwmwXGoWOwdPrsRJOrgz8ZCt/CLkubZYjZ0n3AmLJMJZq6W5i0Rj5CJgMEYNJmMiESY+0w/EeIUcpTkMs8sYAf8CNuAk06Arj5CbnGXy/ih/4Jx3JRR4b4hkdKtxrMi3t6S7laT7KMa+tCUUu+6RB4lXqwc8QP7h3eU739D7mIe0apPuUUUz6kDREeKo9e5T0qL3OuEYseFGXvIzy5/bVvCFNy/C3jPN2W3ohjYs1kkd0aT2hKUKLaW+gKD3ySfJpvQ//MEhjyG3kAvyD8XgE0qxI8XA/DYeUx7S2DJiCY7STXo0HOsB6uIZd1ZKXCTiBRkzu9z19BLwoj42kyEsT+qdYfLHUYIo4Ea+m0N6CeAXue1inRdjkM65dC+OAb8xtSow8PSM9ukmP+1c/ezVXqd4eCZQnaC+McoPvNSzTuIfhWuwPpF/eZkQ5grWT12jEPfj0aayUv87dpPBt4piwYrTPUR5R/iiv40T+oEHPtA/McuSpCK1k2DtoPInZ/4RYM+FAuSgSLvBZeBe45X7F84G4zfg+m1VtuYbLMu20n1/1IzzIO7G/kW/tvL+o2/Xf9ava0rqrGE777eYF5zx/4Iamfj0lbjAW4uvnZZ7Bi4wyV3gcx/WYAzGU9dcN2+7THqLynON7Vz8d5aSZ4UE8/dN4hAMcgAnXu0px4QwjHuC2679fB28LXWrJHhPe73DMPaaFXgn7Au+BdgbvPTZFjgAdySjXDB/RWbVu0jr5vQLfOq2vtFPUL+Kgmyst9/ZGcDrXM5wcLcxH5wHSqkjnLkv/Fv4Rd3WshDpW7BQr9V9j1flbWGEs7rczn2so9zOce6CxmVLstxo8XeBeWJwrNRWemvL9Jx8mHMP8aQo/ynCu86trNU9RT35J50BXIo/r2eRptNe78B/yf9pvyZ/orGls6ZxSxK3RtYxBK2OqYtCKmIr85vtOEdNTwwxadFLvtBf34fblCUroyniiyRV6hrrMnz8l+Uo9fuNJlurPoY0zz6GNP34Oxe3+v8qKTwHv/zVPNv4H</diagram></mxfile>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 29 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 89 KiB |
4173
package-lock.json
generated
4173
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
51
package.json
51
package.json
@@ -1,14 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.7.6",
|
"version": "0.9.2",
|
||||||
"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,50 @@
|
|||||||
"@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",
|
||||||
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
27
src/App.vue
27
src/App.vue
@@ -1,12 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<TheHeader class="row" />
|
<TheHeader class="row" />
|
||||||
<TheSearchBar class="row" />
|
<TheSearchBar class="row" />
|
||||||
<TheScripts class="row"/>
|
<TheScriptArea class="row" />
|
||||||
<TheCodeArea class="row" theme="xcode" />
|
<TheCodeButtons class="row code-buttons" />
|
||||||
<TheCodeButtons class="row code-buttons" />
|
<TheFooter />
|
||||||
<TheFooter />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -15,17 +14,15 @@
|
|||||||
import { Component, Vue } from 'vue-property-decorator';
|
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 TheCodeButtons from '@/presentation/Code/CodeButtons/TheCodeButtons.vue';
|
||||||
import TheCodeButtons from '@/presentation/TheCodeButtons.vue';
|
import TheScriptArea from '@/presentation/Scripts/TheScriptArea.vue';
|
||||||
import TheSearchBar from '@/presentation/TheSearchBar.vue';
|
import TheSearchBar from '@/presentation/TheSearchBar.vue';
|
||||||
import TheScripts from '@/presentation/Scripts/TheScripts.vue';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
TheHeader,
|
TheHeader,
|
||||||
TheCodeArea,
|
|
||||||
TheCodeButtons,
|
TheCodeButtons,
|
||||||
TheScripts,
|
TheScriptArea,
|
||||||
TheSearchBar,
|
TheSearchBar,
|
||||||
TheFooter,
|
TheFooter,
|
||||||
},
|
},
|
||||||
@@ -38,6 +35,7 @@ export default class App extends Vue {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "@/presentation/styles/colors.scss";
|
@import "@/presentation/styles/colors.scss";
|
||||||
@import "@/presentation/styles/fonts.scss";
|
@import "@/presentation/styles/fonts.scss";
|
||||||
|
@import "@/presentation/styles/media.scss";
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -49,12 +47,10 @@ body {
|
|||||||
color: $slate;
|
color: $slate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
max-width: 1500px;
|
max-width: 1600px;
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
margin: 0% 2% 0% 2%;
|
margin: 0% 2% 0% 2%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
@@ -62,18 +58,15 @@ body {
|
|||||||
padding: 2%;
|
padding: 2%;
|
||||||
display:flex;
|
display:flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-buttons {
|
.code-buttons {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@import "@/presentation/styles/tooltip.scss";
|
@import "@/presentation/styles/tooltip.scss";
|
||||||
@import "@/presentation/styles/tree.scss";
|
@import "@/presentation/styles/tree.scss";
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
21
src/application/ApplicationFactory.ts
Normal file
21
src/application/ApplicationFactory.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
||||||
|
import { IApplicationFactory } from './IApplicationFactory';
|
||||||
|
import { parseApplication } from './Parser/ApplicationParser';
|
||||||
|
|
||||||
|
export type ApplicationGetter = () => IApplication;
|
||||||
|
const ApplicationGetter: ApplicationGetter = parseApplication;
|
||||||
|
|
||||||
|
export class ApplicationFactory implements IApplicationFactory {
|
||||||
|
public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter);
|
||||||
|
private readonly getter: AsyncLazy<IApplication>;
|
||||||
|
protected constructor(costlyGetter: ApplicationGetter) {
|
||||||
|
if (!costlyGetter) {
|
||||||
|
throw new Error('undefined getter');
|
||||||
|
}
|
||||||
|
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
|
||||||
|
}
|
||||||
|
public getAppAsync(): Promise<IApplication> {
|
||||||
|
return this.getter.getValueAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/application/Common/Enum.ts
Normal file
43
src/application/Common/Enum.ts
Normal 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[];
|
||||||
|
}
|
||||||
71
src/application/Context/ApplicationContext.ts
Normal file
71
src/application/Context/ApplicationContext.ts
Normal 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 { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
|
|
||||||
|
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
|
||||||
|
|
||||||
|
export class ApplicationContext implements IApplicationContext {
|
||||||
|
public readonly contextChanged = new EventSource<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;
|
||||||
|
}
|
||||||
31
src/application/Context/ApplicationContextFactory.ts
Normal file
31
src/application/Context/ApplicationContextFactory.ts
Normal 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 { IApplicationFactory } from '../IApplicationFactory';
|
||||||
|
import { ApplicationFactory } from '../ApplicationFactory';
|
||||||
|
|
||||||
|
export async function buildContextAsync(
|
||||||
|
factory: IApplicationFactory = ApplicationFactory.Current,
|
||||||
|
environment = Environment.CurrentEnvironment): Promise<IApplicationContext> {
|
||||||
|
if (!factory) { throw new Error('undefined factory'); }
|
||||||
|
if (!environment) { throw new Error('undefined environment'); }
|
||||||
|
const app = await factory.getAppAsync();
|
||||||
|
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];
|
||||||
|
}
|
||||||
16
src/application/Context/IApplicationContext.ts
Normal file
16
src/application/Context/IApplicationContext.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { ICategoryCollectionState } from './State/ICategoryCollectionState';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
|
export interface IApplicationContext {
|
||||||
|
readonly app: IApplication;
|
||||||
|
readonly state: ICategoryCollectionState;
|
||||||
|
readonly contextChanged: IEventSource<IApplicationContextChangedEvent>;
|
||||||
|
changeContext(os: OperatingSystem): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IApplicationContextChangedEvent {
|
||||||
|
readonly newState: ICategoryCollectionState;
|
||||||
|
readonly oldState: ICategoryCollectionState;
|
||||||
|
}
|
||||||
23
src/application/Context/State/CategoryCollectionState.ts
Normal file
23
src/application/Context/State/CategoryCollectionState.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +1,26 @@
|
|||||||
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 { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
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 EventSource<ICodeChangedEvent>();
|
||||||
public current: string;
|
public current: string;
|
||||||
|
|
||||||
private scriptPositions = new Map<SelectedScript, CodePosition>();
|
private scriptPositions = new Map<SelectedScript, CodePosition>();
|
||||||
|
|
||||||
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);
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -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]}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
|
import { ICodeBuilder } from './ICodeBuilder';
|
||||||
|
|
||||||
|
export interface ICodeBuilderFactory {
|
||||||
|
create(language: ScriptingLanguage): ICodeBuilder;
|
||||||
|
}
|
||||||
@@ -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>;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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 ${escapeForEcho(text)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeForEcho(text: string) {
|
||||||
|
return text
|
||||||
|
.replace(/&/g, '^&')
|
||||||
|
.replace(/%/g, '%%');
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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 '${escapeForEcho(text)}'`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeForEcho(text: string) {
|
||||||
|
return text
|
||||||
|
.replace(/'/g, '\'\\\'\'');
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
|
import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
|
||||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
|
|
||||||
export interface IApplicationCode {
|
export interface IApplicationCode {
|
||||||
readonly changed: ISignal<ICodeChangedEvent>;
|
readonly changed: IEventSource<ICodeChangedEvent>;
|
||||||
readonly current: string;
|
readonly current: string;
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
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;
|
||||||
readonly filtered: ISignal<IFilterResult>;
|
readonly filtered: IEventSource<IFilterResult>;
|
||||||
readonly filterRemoved: ISignal<void>;
|
readonly filterRemoved: IEventSource<void>;
|
||||||
setFilter(filter: string): void;
|
setFilter(filter: string): void;
|
||||||
removeFilter(): void;
|
removeFilter(): void;
|
||||||
}
|
}
|
||||||
@@ -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 { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
|
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 EventSource<IFilterResult>();
|
||||||
public readonly filterRemoved = new Signal<void>();
|
public readonly filterRemoved = new EventSource<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,
|
||||||
13
src/application/Context/State/ICategoryCollectionState.ts
Normal file
13
src/application/Context/State/ICategoryCollectionState.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
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 { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
|
|
||||||
export interface IUserSelection {
|
export interface IUserSelection {
|
||||||
readonly changed: ISignal<ReadonlyArray<SelectedScript>>;
|
readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
|
||||||
readonly selectedScripts: ReadonlyArray<SelectedScript>;
|
readonly selectedScripts: ReadonlyArray<SelectedScript>;
|
||||||
readonly totalSelected: number;
|
readonly totalSelected: number;
|
||||||
areAllSelected(category: ICategory): boolean;
|
areAllSelected(category: ICategory): boolean;
|
||||||
@@ -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 { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
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 EventSource<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);
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/application/IApplicationFactory.ts
Normal file
5
src/application/IApplicationFactory.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
|
export interface IApplicationFactory {
|
||||||
|
getAppAsync(): Promise<IApplication>;
|
||||||
|
}
|
||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/application/Parser/CategoryCollectionParser.ts
Normal file
39
src/application/Parser/CategoryCollectionParser.ts
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
import { IScriptCode } from '@/domain/IScriptCode';
|
|
||||||
import { ScriptCode } from '@/domain/ScriptCode';
|
|
||||||
import { YamlScript, YamlFunction, FunctionCall, ScriptFunctionCall, FunctionCallParameters } from 'js-yaml-loader!./application.yaml';
|
|
||||||
import { IScriptCompiler } from './IScriptCompiler';
|
|
||||||
|
|
||||||
interface ICompiledCode {
|
|
||||||
readonly code: string;
|
|
||||||
readonly revertCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ScriptCompiler implements IScriptCompiler {
|
|
||||||
constructor(private readonly functions: readonly YamlFunction[]) {
|
|
||||||
ensureValidFunctions(functions);
|
|
||||||
}
|
|
||||||
public canCompile(script: YamlScript): boolean {
|
|
||||||
if (!script.call) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
public compile(script: YamlScript): IScriptCode {
|
|
||||||
this.ensureCompilable(script.call);
|
|
||||||
const compiledCodes = new Array<ICompiledCode>();
|
|
||||||
const calls = getCallSequence(script.call);
|
|
||||||
calls.forEach((currentCall, currentCallIndex) => {
|
|
||||||
ensureValidCall(currentCall, script.name);
|
|
||||||
const commonFunction = this.getFunctionByName(currentCall.function);
|
|
||||||
let functionCode = compileCode(commonFunction, currentCall.parameters);
|
|
||||||
if (currentCallIndex !== calls.length - 1) {
|
|
||||||
functionCode = appendLine(functionCode);
|
|
||||||
}
|
|
||||||
compiledCodes.push(functionCode);
|
|
||||||
});
|
|
||||||
const scriptCode = merge(compiledCodes);
|
|
||||||
return new ScriptCode(script.name, scriptCode.code, scriptCode.revertCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getFunctionByName(name: string): YamlFunction {
|
|
||||||
const func = this.functions.find((f) => f.name === name);
|
|
||||||
if (!func) {
|
|
||||||
throw new Error(`called function is not defined "${name}"`);
|
|
||||||
}
|
|
||||||
return func;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ensureCompilable(call: ScriptFunctionCall) {
|
|
||||||
if (!this.functions || this.functions.length === 0) {
|
|
||||||
throw new Error('cannot compile without shared functions');
|
|
||||||
}
|
|
||||||
if (typeof call !== 'object') {
|
|
||||||
throw new Error('called function(s) must be an object');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDuplicates(texts: readonly string[]): string[] {
|
|
||||||
return texts.filter((item, index) => texts.indexOf(item) !== index);
|
|
||||||
}
|
|
||||||
|
|
||||||
function printList(list: readonly string[]): string {
|
|
||||||
return `"${list.join('","')}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureNoDuplicatesInFunctionNames(functions: readonly YamlFunction[]) {
|
|
||||||
const duplicateFunctionNames = getDuplicates(functions
|
|
||||||
.map((func) => func.name.toLowerCase()));
|
|
||||||
if (duplicateFunctionNames.length) {
|
|
||||||
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureNoDuplicatesInParameterNames(functions: readonly YamlFunction[]) {
|
|
||||||
const functionsWithParameters = functions
|
|
||||||
.filter((func) => func.parameters && func.parameters.length > 0);
|
|
||||||
for (const func of functionsWithParameters) {
|
|
||||||
const duplicateParameterNames = getDuplicates(func.parameters);
|
|
||||||
if (duplicateParameterNames.length) {
|
|
||||||
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureNoDuplicateCode(functions: readonly YamlFunction[]) {
|
|
||||||
const duplicateCodes = getDuplicates(functions.map((func) => func.code));
|
|
||||||
if (duplicateCodes.length > 0) {
|
|
||||||
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
|
|
||||||
}
|
|
||||||
const duplicateRevertCodes = getDuplicates(functions
|
|
||||||
.filter((func) => func.revertCode)
|
|
||||||
.map((func) => func.revertCode));
|
|
||||||
if (duplicateRevertCodes.length > 0) {
|
|
||||||
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureValidFunctions(functions: readonly YamlFunction[]) {
|
|
||||||
if (!functions) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ensureNoDuplicatesInFunctionNames(functions);
|
|
||||||
ensureNoDuplicatesInParameterNames(functions);
|
|
||||||
ensureNoDuplicateCode(functions);
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendLine(code: ICompiledCode): ICompiledCode {
|
|
||||||
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
|
|
||||||
return {
|
|
||||||
code: appendLineIfNotEmpty(code.code),
|
|
||||||
revertCode: appendLineIfNotEmpty(code.revertCode),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
|
|
||||||
return {
|
|
||||||
code: codes.map((code) => code.code).join(''),
|
|
||||||
revertCode: codes.map((code) => code.revertCode).join(''),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileCode(func: YamlFunction, parameters: FunctionCallParameters): ICompiledCode {
|
|
||||||
return {
|
|
||||||
code: compileExpressions(func.code, parameters),
|
|
||||||
revertCode: compileExpressions(func.revertCode, parameters),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileExpressions(code: string, parameters: FunctionCallParameters): string {
|
|
||||||
let intermediateCode = compileToIL(code);
|
|
||||||
intermediateCode = substituteParameters(intermediateCode, parameters);
|
|
||||||
ensureNoExpressionLeft(intermediateCode);
|
|
||||||
return intermediateCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function substituteParameters(intermediateCode: string, parameters: FunctionCallParameters): string {
|
|
||||||
const parameterNames = getUniqueParameterNamesFromIL(intermediateCode);
|
|
||||||
if (parameterNames.length && !parameters) {
|
|
||||||
throw new Error(`no parameters defined, expected: ${printList(parameterNames)}`);
|
|
||||||
}
|
|
||||||
for (const parameterName of parameterNames) {
|
|
||||||
const parameterValue = parameters[parameterName];
|
|
||||||
intermediateCode = substituteParameter(intermediateCode, parameterName, parameterValue);
|
|
||||||
}
|
|
||||||
return intermediateCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureValidCall(call: FunctionCall, scriptName: string) {
|
|
||||||
if (!call) {
|
|
||||||
throw new Error(`undefined function call in script "${scriptName}"`);
|
|
||||||
}
|
|
||||||
if (!call.function) {
|
|
||||||
throw new Error(`empty function name called in script "${scriptName}"`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCallSequence(call: ScriptFunctionCall): FunctionCall[] {
|
|
||||||
if (call instanceof Array) {
|
|
||||||
return call as FunctionCall[];
|
|
||||||
}
|
|
||||||
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)}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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');
|
||||||
|
|||||||
12
src/application/Parser/ProjectInformationParser.ts
Normal file
12
src/application/Parser/ProjectInformationParser.ts
Normal 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { IExpressionsCompiler, ParameterValueDictionary } from './IExpressionsCompiler';
|
||||||
|
import { generateIlCode, IILCode } from './ILCode';
|
||||||
|
|
||||||
|
export class ExpressionsCompiler implements IExpressionsCompiler {
|
||||||
|
public static readonly instance: IExpressionsCompiler = new ExpressionsCompiler();
|
||||||
|
protected constructor() { }
|
||||||
|
public compileExpressions(code: string, parameters?: ParameterValueDictionary): string {
|
||||||
|
let intermediateCode = generateIlCode(code);
|
||||||
|
intermediateCode = substituteParameters(intermediateCode, parameters);
|
||||||
|
return intermediateCode.compile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function substituteParameters(intermediateCode: IILCode, parameters: ParameterValueDictionary): IILCode {
|
||||||
|
const parameterNames = intermediateCode.getUniqueParameterNames();
|
||||||
|
ensureValuesProvided(parameterNames, parameters);
|
||||||
|
for (const parameterName of parameterNames) {
|
||||||
|
const parameterValue = parameters[parameterName];
|
||||||
|
intermediateCode = intermediateCode.substituteParameter(parameterName, parameterValue);
|
||||||
|
}
|
||||||
|
return intermediateCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureValuesProvided(names: string[], nameValues: ParameterValueDictionary) {
|
||||||
|
nameValues = nameValues || {};
|
||||||
|
const notProvidedNames = names.filter((name) => !Boolean(nameValues[name]));
|
||||||
|
if (notProvidedNames.length) {
|
||||||
|
throw new Error(`parameter value(s) not provided for: ${printList(notProvidedNames)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printList(list: readonly string[]): string {
|
||||||
|
return `"${list.join('", "')}"`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface ParameterValueDictionary { [parameterName: string]: string; }
|
||||||
|
|
||||||
|
export interface IExpressionsCompiler {
|
||||||
|
compileExpressions(code: string, parameters?: ParameterValueDictionary): string;
|
||||||
|
}
|
||||||
73
src/application/Parser/Script/Compiler/Expressions/ILCode.ts
Normal file
73
src/application/Parser/Script/Compiler/Expressions/ILCode.ts
Normal 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('","')}"`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
import { FunctionData, InstructionHolder } from 'js-yaml-loader!*';
|
||||||
|
import { SharedFunction } from './SharedFunction';
|
||||||
|
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
||||||
|
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
|
import { IFunctionCompiler } from './IFunctionCompiler';
|
||||||
|
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
|
||||||
|
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
|
||||||
|
|
||||||
|
export class FunctionCompiler implements IFunctionCompiler {
|
||||||
|
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
|
||||||
|
protected constructor(
|
||||||
|
private readonly functionCallCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance) {
|
||||||
|
}
|
||||||
|
public compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
|
||||||
|
const collection = new SharedFunctionCollection();
|
||||||
|
if (!functions || !functions.length) {
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
ensureValidFunctions(functions);
|
||||||
|
functions
|
||||||
|
.filter((func) => hasCode(func))
|
||||||
|
.forEach((func) => {
|
||||||
|
const shared = new SharedFunction(func.name, func.parameters, func.code, func.revertCode);
|
||||||
|
collection.addFunction(shared);
|
||||||
|
});
|
||||||
|
functions
|
||||||
|
.filter((func) => hasCall(func))
|
||||||
|
.forEach((func) => {
|
||||||
|
const code = this.functionCallCompiler.compileCall(func.call, collection);
|
||||||
|
const shared = new SharedFunction(func.name, func.parameters, code.code, code.revertCode);
|
||||||
|
collection.addFunction(shared);
|
||||||
|
});
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasCode(data: FunctionData): boolean {
|
||||||
|
return Boolean(data.code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasCall(data: FunctionData): boolean {
|
||||||
|
return Boolean(data.call);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
||||||
|
ensureNoUndefinedItem(functions);
|
||||||
|
ensureNoDuplicatesInFunctionNames(functions);
|
||||||
|
ensureNoDuplicatesInParameterNames(functions);
|
||||||
|
ensureNoDuplicateCode(functions);
|
||||||
|
ensureEitherCallOrCodeIsDefined(functions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function printList(list: readonly string[]): string {
|
||||||
|
return `"${list.join('","')}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureEitherCallOrCodeIsDefined(holders: readonly InstructionHolder[]) {
|
||||||
|
// Ensure functions do not define both call and code
|
||||||
|
const withBothCallAndCode = holders.filter((holder) => hasCode(holder) && hasCall(holder));
|
||||||
|
if (withBothCallAndCode.length) {
|
||||||
|
throw new Error(`both "code" and "call" are defined in ${printNames(withBothCallAndCode)}`);
|
||||||
|
}
|
||||||
|
// Ensure functions have either code or call
|
||||||
|
const hasEitherCodeOrCall = holders.filter((holder) => !hasCode(holder) && !hasCall(holder));
|
||||||
|
if (hasEitherCodeOrCall.length) {
|
||||||
|
throw new Error(`neither "code" or "call" is defined in ${printNames(hasEitherCodeOrCall)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function printNames(holders: readonly InstructionHolder[]) {
|
||||||
|
return printList(holders.map((holder) => holder.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
|
||||||
|
const duplicateFunctionNames = getDuplicates(functions
|
||||||
|
.map((func) => func.name.toLowerCase()));
|
||||||
|
if (duplicateFunctionNames.length) {
|
||||||
|
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
|
||||||
|
if (functions.some((func) => !func)) {
|
||||||
|
throw new Error(`some functions are undefined`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ensureNoDuplicatesInParameterNames(functions: readonly FunctionData[]) {
|
||||||
|
const functionsWithParameters = functions
|
||||||
|
.filter((func) => func.parameters && func.parameters.length > 0);
|
||||||
|
for (const func of functionsWithParameters) {
|
||||||
|
const duplicateParameterNames = getDuplicates(func.parameters);
|
||||||
|
if (duplicateParameterNames.length) {
|
||||||
|
throw new Error(`"${func.name}": duplicate parameter name: ${printList(duplicateParameterNames)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
|
||||||
|
const duplicateCodes = getDuplicates(functions
|
||||||
|
.map((func) => func.code)
|
||||||
|
.filter((code) => code),
|
||||||
|
);
|
||||||
|
if (duplicateCodes.length > 0) {
|
||||||
|
throw new Error(`duplicate "code" in functions: ${printList(duplicateCodes)}`);
|
||||||
|
}
|
||||||
|
const duplicateRevertCodes = getDuplicates(functions
|
||||||
|
.filter((func) => func.revertCode)
|
||||||
|
.map((func) => func.revertCode));
|
||||||
|
if (duplicateRevertCodes.length > 0) {
|
||||||
|
throw new Error(`duplicate "revertCode" in functions: ${printList(duplicateRevertCodes)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDuplicates(texts: readonly string[]): string[] {
|
||||||
|
return texts.filter((item, index) => texts.indexOf(item) !== index);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { FunctionData } from 'js-yaml-loader!*';
|
||||||
|
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
|
|
||||||
|
export interface IFunctionCompiler {
|
||||||
|
compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface ISharedFunction {
|
||||||
|
readonly name: string;
|
||||||
|
readonly parameters?: readonly string[];
|
||||||
|
readonly code: string;
|
||||||
|
readonly revertCode?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { ISharedFunction } from './ISharedFunction';
|
||||||
|
|
||||||
|
export interface ISharedFunctionCollection {
|
||||||
|
getFunctionByName(name: string): ISharedFunction;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ISharedFunction } from './ISharedFunction';
|
||||||
|
|
||||||
|
export class SharedFunction implements ISharedFunction {
|
||||||
|
constructor(
|
||||||
|
public readonly name: string,
|
||||||
|
public readonly parameters: readonly string[],
|
||||||
|
public readonly code: string,
|
||||||
|
public readonly revertCode: string,
|
||||||
|
) {
|
||||||
|
if (!name) { throw new Error('undefined function name'); }
|
||||||
|
if (!code) { throw new Error(`undefined function ("${name}") code`); }
|
||||||
|
this.parameters = parameters || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ISharedFunction } from './ISharedFunction';
|
||||||
|
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
|
|
||||||
|
export class SharedFunctionCollection implements ISharedFunctionCollection {
|
||||||
|
private readonly functionsByName = new Map<string, ISharedFunction>();
|
||||||
|
|
||||||
|
public addFunction(func: ISharedFunction): void {
|
||||||
|
if (!func) { throw new Error('undefined function'); }
|
||||||
|
if (this.functionsByName.has(func.name)) {
|
||||||
|
throw new Error(`function with name ${func.name} already exists`);
|
||||||
|
}
|
||||||
|
this.functionsByName.set(func.name, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFunctionByName(name: string): ISharedFunction {
|
||||||
|
if (!name) { throw Error('undefined function name'); }
|
||||||
|
const func = this.functionsByName.get(name);
|
||||||
|
if (!func) {
|
||||||
|
throw new Error(`called function is not defined "${name}"`);
|
||||||
|
}
|
||||||
|
return func;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { FunctionCallData, FunctionCallParametersData, FunctionData, ScriptFunctionCallData } from 'js-yaml-loader!*';
|
||||||
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
|
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
|
||||||
|
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
||||||
|
import { IExpressionsCompiler } from '../Expressions/IExpressionsCompiler';
|
||||||
|
import { ExpressionsCompiler } from '../Expressions/ExpressionsCompiler';
|
||||||
|
|
||||||
|
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
||||||
|
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
||||||
|
protected constructor(
|
||||||
|
private readonly expressionsCompiler: IExpressionsCompiler = ExpressionsCompiler.instance) { }
|
||||||
|
public compileCall(
|
||||||
|
call: ScriptFunctionCallData,
|
||||||
|
functions: ISharedFunctionCollection): ICompiledCode {
|
||||||
|
if (!functions) { throw new Error('undefined functions'); }
|
||||||
|
if (!call) { throw new Error('undefined call'); }
|
||||||
|
const compiledCodes = new Array<ICompiledCode>();
|
||||||
|
const calls = getCallSequence(call);
|
||||||
|
calls.forEach((currentCall, currentCallIndex) => {
|
||||||
|
ensureValidCall(currentCall);
|
||||||
|
const commonFunction = functions.getFunctionByName(currentCall.function);
|
||||||
|
ensureExpectedParameters(commonFunction, currentCall);
|
||||||
|
let functionCode = compileCode(commonFunction, currentCall.parameters, this.expressionsCompiler);
|
||||||
|
if (currentCallIndex !== calls.length - 1) {
|
||||||
|
functionCode = appendLine(functionCode);
|
||||||
|
}
|
||||||
|
compiledCodes.push(functionCode);
|
||||||
|
});
|
||||||
|
const compiledCode = merge(compiledCodes);
|
||||||
|
return compiledCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureExpectedParameters(func: FunctionData, call: FunctionCallData) {
|
||||||
|
if (!func.parameters && !call.parameters) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const unexpectedParameters = Object.keys(call.parameters || {})
|
||||||
|
.filter((callParam) => !func.parameters.includes(callParam));
|
||||||
|
if (unexpectedParameters.length) {
|
||||||
|
throw new Error(
|
||||||
|
`function "${func.name}" has unexpected parameter(s) provided: "${unexpectedParameters.join('", "')}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge(codes: readonly ICompiledCode[]): ICompiledCode {
|
||||||
|
return {
|
||||||
|
code: codes.map((code) => code.code).join(''),
|
||||||
|
revertCode: codes.map((code) => code.revertCode).join(''),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileCode(
|
||||||
|
func: FunctionData,
|
||||||
|
parameters: FunctionCallParametersData,
|
||||||
|
compiler: IExpressionsCompiler): ICompiledCode {
|
||||||
|
return {
|
||||||
|
code: compiler.compileExpressions(func.code, parameters),
|
||||||
|
revertCode: compiler.compileExpressions(func.revertCode, parameters),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCallSequence(call: ScriptFunctionCallData): FunctionCallData[] {
|
||||||
|
if (typeof call !== 'object') {
|
||||||
|
throw new Error('called function(s) must be an object');
|
||||||
|
}
|
||||||
|
if (call instanceof Array) {
|
||||||
|
return call as FunctionCallData[];
|
||||||
|
}
|
||||||
|
return [ call as FunctionCallData ];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureValidCall(call: FunctionCallData) {
|
||||||
|
if (!call) {
|
||||||
|
throw new Error(`undefined function call`);
|
||||||
|
}
|
||||||
|
if (!call.function) {
|
||||||
|
throw new Error(`empty function name called`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendLine(code: ICompiledCode): ICompiledCode {
|
||||||
|
const appendLineIfNotEmpty = (str: string) => str ? `${str}\n` : str;
|
||||||
|
return {
|
||||||
|
code: appendLineIfNotEmpty(code.code),
|
||||||
|
revertCode: appendLineIfNotEmpty(code.revertCode),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface ICompiledCode {
|
||||||
|
readonly code: string;
|
||||||
|
readonly revertCode?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { ScriptFunctionCallData } from 'js-yaml-loader!*';
|
||||||
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
|
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
|
||||||
|
|
||||||
|
export interface IFunctionCallCompiler {
|
||||||
|
compileCall(
|
||||||
|
call: ScriptFunctionCallData,
|
||||||
|
functions: ISharedFunctionCollection): ICompiledCode;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
42
src/application/Parser/Script/Compiler/ScriptCompiler.ts
Normal file
42
src/application/Parser/Script/Compiler/ScriptCompiler.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { IScriptCode } from '@/domain/IScriptCode';
|
||||||
|
import { ScriptCode } from '@/domain/ScriptCode';
|
||||||
|
import { FunctionData, ScriptData } from 'js-yaml-loader!@/*';
|
||||||
|
import { IScriptCompiler } from './IScriptCompiler';
|
||||||
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
|
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
||||||
|
import { IFunctionCallCompiler } from './FunctionCall/IFunctionCallCompiler';
|
||||||
|
import { FunctionCallCompiler } from './FunctionCall/FunctionCallCompiler';
|
||||||
|
import { IFunctionCompiler } from './Function/IFunctionCompiler';
|
||||||
|
import { FunctionCompiler } from './Function/FunctionCompiler';
|
||||||
|
|
||||||
|
export class ScriptCompiler implements IScriptCompiler {
|
||||||
|
private readonly functions: ISharedFunctionCollection;
|
||||||
|
constructor(
|
||||||
|
functions: readonly FunctionData[] | undefined,
|
||||||
|
private readonly syntax: ILanguageSyntax,
|
||||||
|
functionCompiler: IFunctionCompiler = FunctionCompiler.instance,
|
||||||
|
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
||||||
|
) {
|
||||||
|
if (!syntax) { throw new Error('undefined syntax'); }
|
||||||
|
this.functions = functionCompiler.compileFunctions(functions);
|
||||||
|
}
|
||||||
|
public canCompile(script: ScriptData): boolean {
|
||||||
|
if (!script) { throw new Error('undefined script'); }
|
||||||
|
if (!script.call) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public compile(script: ScriptData): IScriptCode {
|
||||||
|
if (!script) { throw new Error('undefined script'); }
|
||||||
|
try {
|
||||||
|
const compiledCode = this.callCompiler.compileCall(script.call, this.functions);
|
||||||
|
return new ScriptCode(
|
||||||
|
compiledCode.code,
|
||||||
|
compiledCode.revertCode,
|
||||||
|
this.syntax);
|
||||||
|
} catch (error) {
|
||||||
|
throw Error(`Script "${script.name}" ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
|
import { IScriptCompiler } from './Compiler/IScriptCompiler';
|
||||||
|
|
||||||
|
export interface ICategoryCollectionParseContext {
|
||||||
|
readonly compiler: IScriptCompiler;
|
||||||
|
readonly syntax: ILanguageSyntax;
|
||||||
|
}
|
||||||
54
src/application/Parser/Script/ScriptParser.ts
Normal file
54
src/application/Parser/Script/ScriptParser.ts
Normal 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, 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);
|
||||||
|
}
|
||||||
6
src/application/Parser/Script/Syntax/BatchFileSyntax.ts
Normal file
6
src/application/Parser/Script/Syntax/BatchFileSyntax.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
|
|
||||||
|
export class BatchFileSyntax implements ILanguageSyntax {
|
||||||
|
public readonly commentDelimiters = [ 'REM', '::' ];
|
||||||
|
public readonly commonCodeParts = [ '(', ')', 'else' ];
|
||||||
|
}
|
||||||
6
src/application/Parser/Script/Syntax/ISyntaxFactory.ts
Normal file
6
src/application/Parser/Script/Syntax/ISyntaxFactory.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
|
|
||||||
|
export interface ISyntaxFactory {
|
||||||
|
create(language: ScriptingLanguage): ILanguageSyntax;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
|
|
||||||
|
export class ShellScriptSyntax implements ILanguageSyntax {
|
||||||
|
public readonly commentDelimiters = [ '#' ];
|
||||||
|
public readonly commonCodeParts = [ '(', ')', 'else' ];
|
||||||
|
}
|
||||||
15
src/application/Parser/Script/Syntax/SyntaxFactory.ts
Normal file
15
src/application/Parser/Script/Syntax/SyntaxFactory.ts
Normal 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]}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
36
src/application/Parser/ScriptingDefinitionParser.ts
Normal file
36
src/application/Parser/ScriptingDefinitionParser.ts
Normal 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/Expressions/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();
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
47
src/application/application.yaml.d.ts
vendored
47
src/application/application.yaml.d.ts
vendored
@@ -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;
|
|
||||||
}
|
|
||||||
59
src/application/collections/collection.yaml.d.ts
vendored
Normal file
59
src/application/collections/collection.yaml.d.ts
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
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 InstructionHolder {
|
||||||
|
readonly name: string;
|
||||||
|
|
||||||
|
readonly code?: string;
|
||||||
|
readonly revertCode?: string;
|
||||||
|
|
||||||
|
readonly call?: ScriptFunctionCallData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FunctionData extends InstructionHolder {
|
||||||
|
readonly parameters?: readonly string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FunctionCallParametersData {
|
||||||
|
readonly [index: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FunctionCallData {
|
||||||
|
readonly function: string;
|
||||||
|
readonly parameters?: FunctionCallParametersData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined;
|
||||||
|
|
||||||
|
export interface ScriptData extends InstructionHolder, DocumentableData {
|
||||||
|
readonly name: string;
|
||||||
|
readonly recommend?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScriptingDefinitionData {
|
||||||
|
readonly language: string;
|
||||||
|
readonly fileExtension: string;
|
||||||
|
readonly startCode: string;
|
||||||
|
readonly endCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content: CollectionData;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
523
src/application/collections/macos.yaml
Normal file
523
src/application/collections/macos.yaml
Normal file
@@ -0,0 +1,523 @@
|
|||||||
|
# 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: Empty trash on all volumes
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
# on all mounted volumes
|
||||||
|
sudo rm -rfv /Volumes/*/.Trashes/* &>/dev/null
|
||||||
|
# on main HDD
|
||||||
|
sudo rm -rfv ~/.Trash/* &>/dev/null
|
||||||
|
-
|
||||||
|
name: Clear system cache files
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
sudo rm -rfv /Library/Caches/* &>/dev/null
|
||||||
|
sudo rm -rfv /System/Library/Caches/* &>/dev/null
|
||||||
|
sudo rm -rfv ~/Library/Caches/* &>/dev/null
|
||||||
|
-
|
||||||
|
name: Clear system log files
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
sudo rm -rfv /private/var/log/asl/*.asl &>/dev/null
|
||||||
|
sudo rm -rfv /Library/Logs/DiagnosticReports/* &>/dev/null
|
||||||
|
sudo rm -rfv /Library/Logs/Adobe/* &>/dev/null
|
||||||
|
rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/* &>/dev/null
|
||||||
|
rm -rfv ~/Library/Logs/CoreSimulator/* &>/dev/null
|
||||||
|
sudo rm -rfv /var/log/*
|
||||||
|
-
|
||||||
|
category: Clear browser history
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
category: Clear Google Chrome history
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Clear Google Chrome browsing history
|
||||||
|
code: |-
|
||||||
|
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History &>/dev/null
|
||||||
|
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History-journal &>/dev/null
|
||||||
|
-
|
||||||
|
name: Google Chrome Cache Files
|
||||||
|
code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null
|
||||||
|
-
|
||||||
|
category: Clear Safari history
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Clear Safari browsing history
|
||||||
|
code: |-
|
||||||
|
rm -f ~/Library/Safari/History.plist
|
||||||
|
rm -f ~/Library/Safari/HistoryIndex.sk
|
||||||
|
-
|
||||||
|
name: Clear Safari downloads history
|
||||||
|
code: rm -f ~/Library/Safari/Downloads.plist
|
||||||
|
-
|
||||||
|
name: Clear Safari top sites
|
||||||
|
code: rm -f ~/Library/Safari/TopSites.plist
|
||||||
|
-
|
||||||
|
name: Clear Safari last session history
|
||||||
|
code: rm -f ~/Library/Safari/LastSession.plist
|
||||||
|
-
|
||||||
|
name: Clear Safari caches
|
||||||
|
code: |-
|
||||||
|
rm -f ~/Library/Caches/com.apple.Safari/Cache.db
|
||||||
|
rm -f ~/Library/Safari/WebpageIcons.db
|
||||||
|
rm -rf ~/Library/Caches/com.apple.Safari/Webpage Previews
|
||||||
|
-
|
||||||
|
name: Clear copy of the Safari history
|
||||||
|
code: rm -rf ~/Library/Caches/Metadata/Safari/History
|
||||||
|
-
|
||||||
|
name: Clear search history embedded in Safari preferences
|
||||||
|
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
|
||||||
|
-
|
||||||
|
name: Clear Safari cookies
|
||||||
|
code: rm -f ~/Library/Cookies/Cookies.plists
|
||||||
|
-
|
||||||
|
name: Clear Safari zoom level preferences per site
|
||||||
|
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plists
|
||||||
|
-
|
||||||
|
name: Clear URLs that are allowed to display notifications in Safari
|
||||||
|
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
|
||||||
|
-
|
||||||
|
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays
|
||||||
|
code: rm -f ~/Library/Safari/PerSitePreferences.db
|
||||||
|
-
|
||||||
|
category: Clear Firefox history
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Clear Firefox cache
|
||||||
|
code: |-
|
||||||
|
sudo rm -rf ~/Library/Caches/Mozilla/
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite
|
||||||
|
-
|
||||||
|
name: Delete Firefox form history
|
||||||
|
code: |-
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.sqlite
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.dat
|
||||||
|
-
|
||||||
|
name: Delete Firefox site preferences
|
||||||
|
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/content-prefs.sqlite
|
||||||
|
-
|
||||||
|
name: Delete Firefox session restore data (loads after the browser closes or crashes)
|
||||||
|
code: |-
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionCheckpoints.json
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore*.js*
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore.bak*
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.js*
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/recovery.js*
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/recovery.bak*
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.bak*
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
|
||||||
|
-
|
||||||
|
name: Delete Firefox passwords
|
||||||
|
docs: http://kb.mozillazine.org/Password_Manager
|
||||||
|
code: |-
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons2.txt
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons3.txt
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json
|
||||||
|
-
|
||||||
|
name: Delete Firefox HTML5 cookies
|
||||||
|
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite
|
||||||
|
-
|
||||||
|
name: Delete Firefox crash reports
|
||||||
|
code: |-
|
||||||
|
rm -rfv ~/Library/Application\ Support/Firefox/Crash\ Reports/
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/minidumps/*.dmp
|
||||||
|
-
|
||||||
|
name: Delete Firefox backup files
|
||||||
|
code: |-
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.json
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.jsonlz4
|
||||||
|
-
|
||||||
|
name: Delete Firefox cookies
|
||||||
|
code: |-
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-shm
|
||||||
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-wal
|
||||||
|
rm -rfv ~/Library/Application\ Support/Firefox/Profiles/*/storage/default/http*
|
||||||
|
-
|
||||||
|
category: Clear third party application data
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Clear Adobe cache
|
||||||
|
recommend: standard
|
||||||
|
code: sudo rm -rfv ~/Library/Application\ Support/Adobe/Common/Media\ Cache\ Files/* &>/dev/null
|
||||||
|
-
|
||||||
|
name: Clear Gradle cache
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if [ -d "/Users/${HOST}/.gradle/caches" ]; then
|
||||||
|
rm -rfv ~/.gradle/caches/ &> /dev/null
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear Dropbox cache
|
||||||
|
recommend: standard
|
||||||
|
code: |-
|
||||||
|
if [ -d "/Users/${HOST}/Dropbox" ]; then
|
||||||
|
sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear Google Drive file stream cache
|
||||||
|
recommend: standard
|
||||||
|
code: |-
|
||||||
|
killall "Google Drive File Stream"
|
||||||
|
rm -rfv ~/Library/Application\ Support/Google/DriveFS/[0-9a-zA-Z]*/content_cache &>/dev/null
|
||||||
|
-
|
||||||
|
name: Clear Composer cache
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if type "composer" &> /dev/null; then
|
||||||
|
composer clearcache &> /dev/null
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear Homebrew cache
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if type "brew" &>/dev/null; then
|
||||||
|
brew cleanup -s &>/dev/null
|
||||||
|
rm -rfv $(brew --cache) &>/dev/null
|
||||||
|
brew tap --repair &>/dev/null
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear any old versions of Ruby gems
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if type "gem" &> /dev/null; then
|
||||||
|
gem cleanup &>/dev/null
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear Docker
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if type "docker" &> /dev/null; then
|
||||||
|
docker system prune -af
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear Pyenv-VirtualEnv cache
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then
|
||||||
|
rm -rfv $PYENV_VIRTUALENV_CACHE_PATH &>/dev/null
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear NPM cache
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if type "npm" &> /dev/null; then
|
||||||
|
npm cache clean --force
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear Yarn cache
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if type "yarn" &> /dev/null; then
|
||||||
|
echo 'Cleanup Yarn Cache...'
|
||||||
|
yarn cache clean --force
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
category: iOS Cleanup
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Clear iOS applications
|
||||||
|
recommend: strict
|
||||||
|
code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null
|
||||||
|
-
|
||||||
|
name: Clear iOS photo caches
|
||||||
|
recommend: standard
|
||||||
|
code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/*
|
||||||
|
-
|
||||||
|
name: Remove iOS Device Backups
|
||||||
|
recommend: strict
|
||||||
|
code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null
|
||||||
|
-
|
||||||
|
name: Clear iOS Simulators
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
if type "xcrun" &>/dev/null; then
|
||||||
|
osascript -e 'tell application "com.apple.CoreSimulator.CoreSimulatorService" to quit'
|
||||||
|
osascript -e 'tell application "iOS Simulator" to quit'
|
||||||
|
osascript -e 'tell application "Simulator" to quit'
|
||||||
|
xcrun simctl shutdown all
|
||||||
|
xcrun simctl erase all
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
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: Clear XCode Derived Data and Archives
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null
|
||||||
|
rm -rfv ~/Library/Developer/Xcode/Archives/* &>/dev/null
|
||||||
|
rm -rfv ~/Library/Developer/Xcode/iOS Device Logs/* &>/dev/null
|
||||||
|
-
|
||||||
|
name: Clear DNS cache
|
||||||
|
recommend: standard
|
||||||
|
code: |-
|
||||||
|
sudo dscacheutil -flushcache
|
||||||
|
sudo killall -HUP mDNSResponder
|
||||||
|
-
|
||||||
|
name: Purge inactive memory
|
||||||
|
recommend: standard
|
||||||
|
code: sudo purge
|
||||||
|
-
|
||||||
|
category: Reset privacy permissions for all applications
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Reset camera permissions
|
||||||
|
code: tccutil reset Camera
|
||||||
|
-
|
||||||
|
name: Reset microphone permissions
|
||||||
|
code: tccutil reset Microphone
|
||||||
|
-
|
||||||
|
name: Reset accessibility permissions
|
||||||
|
code: tccutil reset Accessibility
|
||||||
|
-
|
||||||
|
name: Reset screen capture permissions
|
||||||
|
code: tccutil reset ScreenCapture
|
||||||
|
-
|
||||||
|
name: Reset reminders permissions
|
||||||
|
code: tccutil reset Reminders
|
||||||
|
-
|
||||||
|
name: Reset photos permissions
|
||||||
|
code: tccutil reset Photos
|
||||||
|
-
|
||||||
|
name: Reset calendar permissions
|
||||||
|
code: tccutil reset Calendar
|
||||||
|
-
|
||||||
|
name: Reset full disk access permissions
|
||||||
|
code: tccutil reset SystemPolicyAllFiles
|
||||||
|
-
|
||||||
|
name: Reset contacts permissions
|
||||||
|
code: tccutil reset SystemPolicyAllFiles
|
||||||
|
-
|
||||||
|
name: Reset desktop folder permissions
|
||||||
|
code: tccutil reset SystemPolicyDesktopFolder
|
||||||
|
-
|
||||||
|
name: Reset documents folder permissions
|
||||||
|
code: tccutil reset SystemPolicyDocumentsFolder
|
||||||
|
-
|
||||||
|
name: Reset downloads permissions
|
||||||
|
code: tccutil reset SystemPolicyDownloadsFolder
|
||||||
|
-
|
||||||
|
name: Reset all app permissions
|
||||||
|
code: 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
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
168
src/domain/CategoryCollection.ts
Normal file
168
src/domain/CategoryCollection.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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';
|
|
||||||
|
|||||||
19
src/domain/ICategoryCollection.ts
Normal file
19
src/domain/ICategoryCollection.ts
Normal 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>;
|
||||||
|
}
|
||||||
8
src/domain/IScriptingDefinition.ts
Normal file
8
src/domain/IScriptingDefinition.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { ScriptingLanguage } from './ScriptingLanguage';
|
||||||
|
|
||||||
|
export interface IScriptingDefinition {
|
||||||
|
readonly fileExtension: string;
|
||||||
|
readonly language: ScriptingLanguage;
|
||||||
|
readonly startCode: string;
|
||||||
|
readonly endCode: string;
|
||||||
|
}
|
||||||
@@ -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[];
|
|
||||||
|
|||||||
@@ -2,56 +2,61 @@ 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) {
|
syntax: ILanguageSyntax) {
|
||||||
throw new Error('script name is undefined');
|
if (!syntax) { throw new Error('undefined syntax'); }
|
||||||
}
|
validateCode(execute, syntax);
|
||||||
validateCode(scriptName, execute);
|
|
||||||
if (revert) {
|
if (revert) {
|
||||||
scriptName = `${scriptName} (revert)`;
|
try {
|
||||||
validateCode(scriptName, revert);
|
validateCode(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(`Code itself and its reverting code cannot be the same`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw Error(`(revert): ${err.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateCode(name: string, code: string): void {
|
export interface ILanguageSyntax {
|
||||||
|
readonly commentDelimiters: string[];
|
||||||
|
readonly commonCodeParts: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateCode(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 is empty or undefined`);
|
||||||
}
|
}
|
||||||
ensureCodeHasUniqueLines(name, code);
|
ensureNoEmptyLines(code);
|
||||||
ensureNoEmptyLines(name, code);
|
ensureCodeHasUniqueLines(code, syntax);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureNoEmptyLines(name: string, code: string): void {
|
function ensureNoEmptyLines(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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureCodeHasUniqueLines(name: string, code: string): void {
|
function ensureCodeHasUniqueLines(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;
|
||||||
}
|
}
|
||||||
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
|
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
|
||||||
if (duplicateLines.length !== 0) {
|
if (duplicateLines.length !== 0) {
|
||||||
throw Error(`Duplicates detected in script "${name}":\n ${duplicateLines.join('\n')}`);
|
throw Error(`Duplicates detected in script :\n ${duplicateLines.join('\n')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/domain/ScriptingDefinition.ts
Normal file
32
src/domain/ScriptingDefinition.ts
Normal 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}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user