Compare commits
76 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0db8cc4206 | ||
|
|
97ddc027cb | ||
|
|
82c43ba2e3 | ||
|
|
799fb091b8 | ||
|
|
5ead1a087d | ||
|
|
64631a4552 | ||
|
|
f47cb04860 | ||
|
|
504fa056d7 | ||
|
|
739287ac71 | ||
|
|
ab8bce7686 | ||
|
|
e6152fa76f | ||
|
|
a8031d18d5 | ||
|
|
9aa8166891 | ||
|
|
236a0f6c82 | ||
|
|
2492f2d814 | ||
|
|
410bcd8244 | ||
|
|
b08a6b5cec | ||
|
|
37ad26a082 | ||
|
|
0696ed8396 | ||
|
|
9942df16c8 | ||
|
|
20b7d283b0 | ||
|
|
f39ee76c0c | ||
|
|
4b2390736a | ||
|
|
c8cb7a5c28 | ||
|
|
5217b0b758 | ||
|
|
ddf417a16a | ||
|
|
2f0321f315 | ||
|
|
4d7ff7edc5 | ||
|
|
862914b06e | ||
|
|
6c3c2e6709 | ||
|
|
c92dc1e253 | ||
|
|
e73c0ad1bf | ||
|
|
6a89c6224b | ||
|
|
dcccb61781 | ||
|
|
c0c475ff56 | ||
|
|
6dc768817f | ||
|
|
439cd303ff | ||
|
|
ec0c972d34 | ||
|
|
2a08855e5d | ||
|
|
1c6b3057ea | ||
|
|
ea5f9ec27d | ||
|
|
f2935e4008 | ||
|
|
487001af48 | ||
|
|
71e70e50c5 | ||
|
|
0a857aa09e | ||
|
|
b976b92031 | ||
|
|
db62ed7f3a | ||
|
|
36f0805590 | ||
|
|
49600c5f37 | ||
|
|
eb9ac35a92 | ||
|
|
77148980e0 | ||
|
|
b3d2e82025 | ||
|
|
b25b8cc805 | ||
|
|
8141a01ef7 | ||
|
|
a2f10857e2 | ||
|
|
aea04e5f7c | ||
|
|
60c80611ea | ||
|
|
b1ed3ce55f | ||
|
|
040ed2701c | ||
|
|
00d8e551db | ||
|
|
3e9c99f5f8 | ||
|
|
02bdc4cf04 | ||
|
|
5c43965f0b | ||
|
|
b2376ecc30 | ||
|
|
aeaa6deeb4 | ||
|
|
448e378dc4 | ||
|
|
ac2249f256 | ||
|
|
05932c5a36 | ||
|
|
6f46cdb4ed | ||
|
|
5f527a00cf | ||
|
|
1935db1019 | ||
|
|
1f515e7be5 | ||
|
|
1a5f92021f | ||
|
|
f3c7413f52 | ||
|
|
646db90585 | ||
|
|
1f8a0cf9ab |
32
.github/ISSUE_TEMPLATE/1-bug-report-scripts.md
vendored
32
.github/ISSUE_TEMPLATE/1-bug-report-scripts.md
vendored
@@ -11,9 +11,11 @@ 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.
|
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
|
### Description
|
||||||
|
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
<!--
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
-->
|
||||||
|
|
||||||
### OS
|
### OS
|
||||||
|
|
||||||
@@ -23,14 +25,32 @@ 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".
|
On macOS you can find it using "Apple menu (top left corner)" > "About This Mac".
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Screenshots
|
### Reproduction steps
|
||||||
|
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
<!--
|
||||||
|
How can the bug be recreated?
|
||||||
|
It's the most important information in the bug report. Bugs that cannot be reproduced cannot be fixed and verified.
|
||||||
|
E.g.
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
-->
|
||||||
|
|
||||||
### Scripts
|
### Scripts
|
||||||
|
|
||||||
<!-- Which scripts did you execute? If applicable, please paste the executed scripts or attach the generated privacy.sexy file . -->
|
<!--
|
||||||
|
If applicable, please attach the generated privacy.sexy file instead of copy pasting which becomes too long.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
-->
|
||||||
|
|
||||||
### Additional information
|
### Additional information
|
||||||
|
|
||||||
<!-- Add any other context about the problem here. -->
|
<!--
|
||||||
|
If applicable, add any other context about the problem here.
|
||||||
|
-->
|
||||||
|
|||||||
17
.github/ISSUE_TEMPLATE/2-bug-report-generic.md
vendored
17
.github/ISSUE_TEMPLATE/2-bug-report-generic.md
vendored
@@ -11,13 +11,16 @@ 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.
|
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
|
### Description
|
||||||
|
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
<!--
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
-->
|
||||||
|
|
||||||
### To Reproduce
|
### Reproduction steps
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
It's the most important information in the bug report. Bugs that cannot be reproduced cannot be fixed and verified.
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
@@ -41,12 +44,12 @@ If applicable, add screenshots to help explain your problem.
|
|||||||
|
|
||||||
<!--
|
<!--
|
||||||
If applicable, mention how you were using privacy.sexy when the bug was encountered:
|
If applicable, mention how you were using privacy.sexy when the bug was encountered:
|
||||||
- Web (on Desktop or mobile?)
|
- Web (on Desktop or mobile?)
|
||||||
- Or desktop (Windows, macOS or Linux?)
|
- Or desktop (Windows, macOS or Linux?)
|
||||||
-->
|
-->
|
||||||
|
|
||||||
### Additional context
|
### Additional context
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Add any other context about the problem here.
|
If applicable, add any other context about the problem here.
|
||||||
-->
|
-->
|
||||||
|
|||||||
36
.github/ISSUE_TEMPLATE/3-feature-request.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/3-feature-request.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for privacy.sexy
|
||||||
|
labels: enhancement
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for suggesting an idea to improve privacy better 🤗.
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Problem description
|
||||||
|
|
||||||
|
<!--
|
||||||
|
What are we trying to solve?
|
||||||
|
Please add a clear and concise description of the problem you are seeking to solve with this feature request.
|
||||||
|
E.g. 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
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, add any other context or screenshots about the feature request here.
|
||||||
|
-->
|
||||||
27
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
27
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
@@ -1,27 +0,0 @@
|
|||||||
---
|
|
||||||
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. -->
|
|
||||||
73
.github/ISSUE_TEMPLATE/4-new-script-suggestion.md
vendored
Normal file
73
.github/ISSUE_TEMPLATE/4-new-script-suggestion.md
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
---
|
||||||
|
name: New script suggestion
|
||||||
|
about: Suggest a new script for privacy.sexy
|
||||||
|
labels: enhancement
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for suggesting an script to make privacy better. 🤗
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
You could alternatively send a PR directly (see CONTRIBUTING.md).
|
||||||
|
-->
|
||||||
|
|
||||||
|
### OS
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Which OS will the new script configure?
|
||||||
|
Either "Windows" or "macOS".
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Name
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The name of the script.
|
||||||
|
It should start with an imperative noun such as "disable", "turn off" , "clear"...
|
||||||
|
E.g. "Disable webcam telemetry"
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Script code
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Code that will be executed when script is selected.
|
||||||
|
Try to keep it as simple and backwards-compatible as possible.
|
||||||
|
Allowed languages:
|
||||||
|
- macOS: bash (sh)
|
||||||
|
- Windows: PowerShell (ps1) or batchfile
|
||||||
|
- 💡 Prioritize the one that's simpler, batchfile if similar.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Revert code
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, add code that will revert the script code to its original (OS default) state.
|
||||||
|
It may require additional time, but it's much appreciated by the community.
|
||||||
|
Leave blank if the script is nonreversible (e.g. when clearing data without backup).
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Suggested category
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, suggest one more multiple suitable parent category of script.
|
||||||
|
A category is the item where the script will be presented under.
|
||||||
|
Most likely there already is a category for the script, so check the existing categories.
|
||||||
|
If you're unsure, leave blank and maintainer(s) will choose one.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Suggested recommendation level
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, suggest recommending the script or not recommending at all.
|
||||||
|
A script should be only recommended if it'll be safe for your grandmother to run.
|
||||||
|
So you have three options here:
|
||||||
|
STANDARD: Non-breaking scripts that does not limit any functionality.
|
||||||
|
STRICT: Scripts that can break certain functionality but not intrusive to common daily OS usage.
|
||||||
|
NONE: Script is not recommended for newbies at all, only those who knows what's going on should select it.
|
||||||
|
If you're unsure, leave blank and maintainer(s) will choose one.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Additional documentation/references
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, refer to documentation that should show up on the script description.
|
||||||
|
Sources (URLs) should be as high quality as possible e.g. vendor documentation is favored over user forums.
|
||||||
|
-->
|
||||||
4
.github/workflows/deploy-desktop.yaml
vendored
4
.github/workflows/deploy-desktop.yaml
vendored
@@ -22,10 +22,10 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '14.x'
|
node-version: 15.x
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Run tests
|
- name: Run unit tests
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
- 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
|
||||||
|
|||||||
4
.github/workflows/deploy-site.yaml
vendored
4
.github/workflows/deploy-site.yaml
vendored
@@ -83,13 +83,13 @@ jobs:
|
|||||||
name: "App: Setup node"
|
name: "App: Setup node"
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '14.x'
|
node-version: 15.x
|
||||||
-
|
-
|
||||||
name: "App: Install dependencies"
|
name: "App: Install dependencies"
|
||||||
run: npm ci
|
run: npm ci
|
||||||
working-directory: site
|
working-directory: site
|
||||||
-
|
-
|
||||||
name: "App: Run tests"
|
name: "App: Run unit tests"
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
working-directory: site
|
working-directory: site
|
||||||
-
|
-
|
||||||
|
|||||||
3
.github/workflows/quality-checks.yaml
vendored
3
.github/workflows/quality-checks.yaml
vendored
@@ -13,13 +13,14 @@ jobs:
|
|||||||
- npm run lint:md
|
- npm run lint:md
|
||||||
- npm run lint:md:relative-urls
|
- npm run lint:md:relative-urls
|
||||||
- npm run lint:md:consistency
|
- npm run lint:md:consistency
|
||||||
|
fail-fast: false # So it continues with other commands if one fails
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 15.x
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Lint
|
- name: Lint
|
||||||
|
|||||||
6
.github/workflows/security-checks.yaml
vendored
6
.github/workflows/security-checks.yaml
vendored
@@ -5,7 +5,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths: [ '/package.json', '/package-lock.json' ] # Allow PRs to be green if they do not introduce dependency change
|
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' # at 00:00 on every Sunday
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
npm-audit:
|
npm-audit:
|
||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
name: Setup node
|
name: Setup node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: 14.x
|
node-version: 15.x
|
||||||
-
|
-
|
||||||
name: NPM audit
|
name: NPM audit
|
||||||
run: npm audit
|
run: exit "$(npm audit)" # Since node 15.x, it does not fail with error if we don't explicitly exit
|
||||||
|
|||||||
14
.github/workflows/test.yaml
vendored
14
.github/workflows/test.yaml
vendored
@@ -1,12 +1,17 @@
|
|||||||
name: Test
|
name: Test
|
||||||
|
|
||||||
on: [ push, pull_request ]
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule: # for integration tests
|
||||||
|
- cron: '0 0 * * 0' # at 00:00 on every Sunday
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-tests:
|
run-tests:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos, ubuntu, windows]
|
os: [macos, ubuntu, windows]
|
||||||
|
fail-fast: false # So it still runs on other OSes if one of them fails
|
||||||
runs-on: ${{ matrix.os }}-latest
|
runs-on: ${{ matrix.os }}-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
@@ -16,10 +21,13 @@ jobs:
|
|||||||
name: Setup node
|
name: Setup node
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
with:
|
with:
|
||||||
node-version: '14.x'
|
node-version: 15.x
|
||||||
-
|
-
|
||||||
name: Install dependencies
|
name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
-
|
-
|
||||||
name: Run tests
|
name: Run unit tests
|
||||||
run: npm run test:unit
|
run: npm run test:unit
|
||||||
|
-
|
||||||
|
name: Run integration tests
|
||||||
|
run: npm run test:integration
|
||||||
|
|||||||
100
CHANGELOG.md
100
CHANGELOG.md
@@ -1,5 +1,105 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.11.0 (2021-10-21)
|
||||||
|
|
||||||
|
* Change "grouping" to "view" | [c0c475f](https://github.com/undergroundwires/privacy.sexy/commit/c0c475ff564b23a4dabcc03ac2909207a8eb61ce)
|
||||||
|
* Tighten parameter substitution tolerance | [dcccb61](https://github.com/undergroundwires/privacy.sexy/commit/dcccb617813625c224a28242c5b965bb4cd6f189)
|
||||||
|
* Add optionality for parameters | [6a89c62](https://github.com/undergroundwires/privacy.sexy/commit/6a89c6224bdef5eb96980471f3b3935b9351b197)
|
||||||
|
* Do not collapse cards on links and code area #88 | [e73c0ad](https://github.com/undergroundwires/privacy.sexy/commit/e73c0ad1bf922b1dd3360fc5aafc3434951fa63c)
|
||||||
|
* Add scripts to disable, hide and opt-out from Siri | [c92dc1e](https://github.com/undergroundwires/privacy.sexy/commit/c92dc1e25387c65a3a41ca64d2a23cf8131b4c86)
|
||||||
|
* Improve macOS scripts for cleaning OS logs | [6c3c2e6](https://github.com/undergroundwires/privacy.sexy/commit/6c3c2e6709ec84f8e0411f19c024bab2c7e5753b)
|
||||||
|
* Add "with" expression for templating #53 | [862914b](https://github.com/undergroundwires/privacy.sexy/commit/862914b06ea9ef74c4b58a9a4164a10a38273638)
|
||||||
|
* Add support for pipes in templates #53 | [4d7ff7e](https://github.com/undergroundwires/privacy.sexy/commit/4d7ff7edc5a96cc0d99d3c1ca4fdf9bbdace3fd2)
|
||||||
|
* Bump node environment to 15.x | [2f0321f](https://github.com/undergroundwires/privacy.sexy/commit/2f0321f315ac0da8c713dd50e37032f1de194942)
|
||||||
|
* Add new UX for optionally downloading updates | [ddf417a](https://github.com/undergroundwires/privacy.sexy/commit/ddf417a16a79551b43576befab0541ea08487969)
|
||||||
|
* Add pipes to write pretty PowerShell #53 | [5217b0b](https://github.com/undergroundwires/privacy.sexy/commit/5217b0b7587ccfe509ba8adc3a7748b9bae14d7a)
|
||||||
|
* Improve alignment, padding/margin issues on UI | [c8cb7a5](https://github.com/undergroundwires/privacy.sexy/commit/c8cb7a5c28420557319606da82f56b011e88f470)
|
||||||
|
* Support disabling per-user services in Windows #16 | [4b23907](https://github.com/undergroundwires/privacy.sexy/commit/4b2390736ac1f9de2d5176b7b07da0e827112f9a)
|
||||||
|
* Add script to remove Meet Now icon in Windows | [f39ee76](https://github.com/undergroundwires/privacy.sexy/commit/f39ee76c0cda95f54502b19d5c49390fd0f12b5e)
|
||||||
|
* Add support for more depth in function calls | [20b7d28](https://github.com/undergroundwires/privacy.sexy/commit/20b7d283b02dd751dfbde18ef1fe334c6bf76e2b)
|
||||||
|
* Increase default screen width on desktop app | [9942df1](https://github.com/undergroundwires/privacy.sexy/commit/9942df16c8334ff041fb92f432a3a29e351c88df)
|
||||||
|
* Improve disabling of SmartScreen #74 | [0696ed8](https://github.com/undergroundwires/privacy.sexy/commit/0696ed8396e298a358bec17adb91c9145dd90418)
|
||||||
|
* Remove integration tests from deployments #90 | [37ad26a](https://github.com/undergroundwires/privacy.sexy/commit/37ad26a082851c02497c36e7fce40555b9480e11)
|
||||||
|
* Use a consistent color system | [b08a6b5](https://github.com/undergroundwires/privacy.sexy/commit/b08a6b5cecf4a53023053695292146edbd24b960)
|
||||||
|
* Add semi-automatic update support for macOS | [410bcd8](https://github.com/undergroundwires/privacy.sexy/commit/410bcd82445097c29c9fcf0eabf7af9ebcb93c1e)
|
||||||
|
* Add more ways to disable and clean Defender #74 | [2492f2d](https://github.com/undergroundwires/privacy.sexy/commit/2492f2d8141b3abdf590ccad59680b1f50ecb59e)
|
||||||
|
* Add privacy over security scripts for macOS #83 | [236a0f6](https://github.com/undergroundwires/privacy.sexy/commit/236a0f6c8241294fc397194cd1b20bdeccbbb50b)
|
||||||
|
* Change PowerShell double quotes escape | [9aa8166](https://github.com/undergroundwires/privacy.sexy/commit/9aa816689146ee6cd86d8262112677c38651c6bd)
|
||||||
|
* Change theme colors | [a8031d1](https://github.com/undergroundwires/privacy.sexy/commit/a8031d18d520dd3b0567f7b8cfe2dcd694b65073)
|
||||||
|
* Improve security hardening for macOS | [e6152fa](https://github.com/undergroundwires/privacy.sexy/commit/e6152fa76f5e7d23b0f79d5dd98713daaecbff90)
|
||||||
|
* Support disabling of protected services #74 | [ab8bce7](https://github.com/undergroundwires/privacy.sexy/commit/ab8bce768650a10677f0a13b3a9fae93c83802ff)
|
||||||
|
* Fix minor issues with Defender scripts | [739287a](https://github.com/undergroundwires/privacy.sexy/commit/739287ac71b3f8b04348fc101f1fa06f2d7d86a2)
|
||||||
|
* Update screenshot | [504fa05](https://github.com/undergroundwires/privacy.sexy/commit/504fa056d7d8b17fc20afd398f9a557495fca7e8)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.10.3...0.11.0)
|
||||||
|
|
||||||
|
## 0.10.3 (2021-08-27)
|
||||||
|
|
||||||
|
* unrecommend VSS and document its breaking behavior | [7714898](https://github.com/undergroundwires/privacy.sexy/commit/77148980e08859f89c15c6604e55b56ce4f74358)
|
||||||
|
* fix incorrect modification of Desktop folder on ThisPC (#71) | [eb9ac35](https://github.com/undergroundwires/privacy.sexy/commit/eb9ac35a923325cc2c9983ef71c0d904337a58f5)
|
||||||
|
* add initial integration tests | [49600c5](https://github.com/undergroundwires/privacy.sexy/commit/49600c5f37ca33c1687885fdf02a71ef7d3e6e8c)
|
||||||
|
* unify usage of sleepAsync and add tests | [36f0805](https://github.com/undergroundwires/privacy.sexy/commit/36f08055909f371fd9cbe3480ea813b963aea22b)
|
||||||
|
* fix broken URLs and automate broken URL checks #70 | [db62ed7](https://github.com/undergroundwires/privacy.sexy/commit/db62ed7f3ac63e9f2d762eb946060595eb9f5626)
|
||||||
|
* fix hiding recent files in quick access | [b976b92](https://github.com/undergroundwires/privacy.sexy/commit/b976b920318dba55b32d39f148fdca4f6be3cce3)
|
||||||
|
* bump dependencies to latest #75, #69 | [0a857aa](https://github.com/undergroundwires/privacy.sexy/commit/0a857aa09ee703d34ad0422bd1731158017a9a58)
|
||||||
|
* Fix NTP configuration before running the service (#72) | [71e70e5](https://github.com/undergroundwires/privacy.sexy/commit/71e70e50c51249bb10f6203414948b325acc2b2a)
|
||||||
|
* Fix typo on main page (#82) | [487001a](https://github.com/undergroundwires/privacy.sexy/commit/487001af485fdbb958615d7b52c09c2e386ddaf2)
|
||||||
|
* Improve issue templates | [f2935e4](https://github.com/undergroundwires/privacy.sexy/commit/f2935e4008f1231ef174f8932290e11715564d20)
|
||||||
|
* Fix infinitely subscribing to state changes | [ea5f9ec](https://github.com/undergroundwires/privacy.sexy/commit/ea5f9ec27df7cec6ac575e23fef18948d2b8e68a)
|
||||||
|
* Fix select options being clickable when disabled | [1c6b305](https://github.com/undergroundwires/privacy.sexy/commit/1c6b3057ea6e45125cadf374f20a905712ccdf3c)
|
||||||
|
* Fix tests for `ParameterSubstitutionParser` | [2a08855](https://github.com/undergroundwires/privacy.sexy/commit/2a08855e5d1bdf74354fd692cbfebd1a48e495ac)
|
||||||
|
* Fix excessive highlighting on hover | [ec0c972](https://github.com/undergroundwires/privacy.sexy/commit/ec0c972d348ffd5897f115d201031b704875b56a)
|
||||||
|
* Fix dead URLs | [439cd30](https://github.com/undergroundwires/privacy.sexy/commit/439cd303ff3db96a53664e5f44fefe12b95c5e6c)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.10.2...0.10.3)
|
||||||
|
|
||||||
|
## 0.10.2 (2021-04-19)
|
||||||
|
|
||||||
|
* in CI/CD, run other tests/check even if one of them fails | [5c43965](https://github.com/undergroundwires/privacy.sexy/commit/5c43965f0bc44f991ada7d3bad68937a80665dc3)
|
||||||
|
* fix desktop initial window size being bigger than current display size on smaller Linux/Windows screens | [02bdc4c](https://github.com/undergroundwires/privacy.sexy/commit/02bdc4cf0426c452f3fc9af52b819ca9b0757290)
|
||||||
|
* refactor extra code, duplicates, complexity | [00d8e55](https://github.com/undergroundwires/privacy.sexy/commit/00d8e551db001247fadfb6f6af7a4c5ce19a9e64)
|
||||||
|
* improve disabling ads and marketing #65 | [040ed27](https://github.com/undergroundwires/privacy.sexy/commit/040ed2701c4a468749901f4c5369b221bc0973c4)
|
||||||
|
* document breaking behavior in script name #64 | [b1ed3ce](https://github.com/undergroundwires/privacy.sexy/commit/b1ed3ce55f2d003cad1ead23e674aa66d4eb5802)
|
||||||
|
* add module alias '@tests/' | [60c8061](https://github.com/undergroundwires/privacy.sexy/commit/60c80611eab227791fabb883caf93418cef5fd00)
|
||||||
|
* document chromium warning for policy changes | [aea04e5](https://github.com/undergroundwires/privacy.sexy/commit/aea04e5f7cd48fbb9b407b68ade75575a6064c82)
|
||||||
|
* fix script revert activating recommendation level | [a2f1085](https://github.com/undergroundwires/privacy.sexy/commit/a2f10857e2a8debb3ce01f79b0dfbe8649ea9a17)
|
||||||
|
* fix typo and dead URL in Windows scripts (#70) | [8141a01](https://github.com/undergroundwires/privacy.sexy/commit/8141a01ef798331b4d82f5ca95f7b18df4f6f912)
|
||||||
|
* fix vue warning for undefined property during render | [b25b8cc](https://github.com/undergroundwires/privacy.sexy/commit/b25b8cc8052655af70b0695c6c3085974d783bb6)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.10.1...0.10.2)
|
||||||
|
|
||||||
|
## 0.10.1 (2021-03-25)
|
||||||
|
|
||||||
|
* refactor script compilation to make it easy to add new expressions #41 #53 | [646db90](https://github.com/undergroundwires/privacy.sexy/commit/646db9058541cebd0af437554de04fdc6bb63a6e)
|
||||||
|
* restructure presentation layer | [f3c7413](https://github.com/undergroundwires/privacy.sexy/commit/f3c7413f529be4a00dba7b0ab23904b48ea13a35)
|
||||||
|
* fix a test where "it" is not used inside "describe" | [1a5f920](https://github.com/undergroundwires/privacy.sexy/commit/1a5f92021f7423cd039f8f5326cd6f99b355c962)
|
||||||
|
* bump dependencies to latest | [1f515e7](https://github.com/undergroundwires/privacy.sexy/commit/1f515e7be525291c960ccb71db05312db6da53f5)
|
||||||
|
* fix throttle function not being able to run with argument(s) | [1935db1](https://github.com/undergroundwires/privacy.sexy/commit/1935db10192051401ab00ca2cd767955d0d3b866)
|
||||||
|
* fix fs module hanging not allowing code to run | [5f527a0](https://github.com/undergroundwires/privacy.sexy/commit/5f527a00cf225d3e74b3f6577d6e2456e919de24)
|
||||||
|
* refactor all modals to use same dialog component | [6f46cdb](https://github.com/undergroundwires/privacy.sexy/commit/6f46cdb4ed49a8941c6c0dde5c5e2a816c06daef)
|
||||||
|
* fix safari cleanup scripts that are not working on modern versions | [05932c5](https://github.com/undergroundwires/privacy.sexy/commit/05932c5a36446d551c5bc811165e3295fbe15e3f)
|
||||||
|
* refactor features to use shared functions #41 | [ac2249f](https://github.com/undergroundwires/privacy.sexy/commit/ac2249f25664827d8a6d2c7ebd659ccf126b0cde)
|
||||||
|
* increase performance by polyfilling ResizeObserver only if required | [448e378](https://github.com/undergroundwires/privacy.sexy/commit/448e378dc4501f9de69af63634c87d0e5060bf52)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.10.0...0.10.1)
|
||||||
|
|
||||||
|
## 0.10.0 (2021-03-02)
|
||||||
|
|
||||||
|
* allow functions to call other functions #53 | [7661575](https://github.com/undergroundwires/privacy.sexy/commit/7661575573c6d3e8f4bc28bfa7a124a764c72ef9)
|
||||||
|
* add option to run script directly in desktop app | [9a6b903](https://github.com/undergroundwires/privacy.sexy/commit/9a6b903b9297802845043fd41115756acd4a145c)
|
||||||
|
* add script to automatically kill devicecensus process | [c9b91f6](https://github.com/undergroundwires/privacy.sexy/commit/c9b91f6d8f9bd16308b6beda119e7154a985b6cf)
|
||||||
|
* refactor disabling application experience and document better | [45a3669](https://github.com/undergroundwires/privacy.sexy/commit/45a3669443d82855a52f60524d341c15f380f9e7)
|
||||||
|
* escape printed characters to prevent command injection #45 | [1260eea](https://github.com/undergroundwires/privacy.sexy/commit/1260eea690e4fa5420e58c9de9f88cc29cb242db)
|
||||||
|
* move code area to right on bigger screens | [cf39e6d](https://github.com/undergroundwires/privacy.sexy/commit/cf39e6d2541ea547f41d9553c380c54c24c58038)
|
||||||
|
* more scripts to disable speech recognition and Cortana | [ee43fd9](https://github.com/undergroundwires/privacy.sexy/commit/ee43fd92a019ebd26c13890f9146c5b5bb56afaf)
|
||||||
|
* add more macos scripts for privacy cleanup | [b0a7d0b](https://github.com/undergroundwires/privacy.sexy/commit/b0a7d0b53b3d8ac144a0241d70c037f460b0c0cc)
|
||||||
|
* add better error messages to setting vscode settings | [65226f3](https://github.com/undergroundwires/privacy.sexy/commit/65226f3984480d0bc7932fd8d76a328f08308850)
|
||||||
|
* remove windows scripts for removing non-bloating system apps #55 | [15004ff](https://github.com/undergroundwires/privacy.sexy/commit/15004ff1f1fb85a1d92e11ef695bcb2f37110610)
|
||||||
|
* remove "preview" disclaimer from macOS | [970221b](https://github.com/undergroundwires/privacy.sexy/commit/970221b996e25fe5b029cbaa78607c9bbc8c3c0e)
|
||||||
|
* update screenshot | [bd41af4](https://github.com/undergroundwires/privacy.sexy/commit/bd41af466fd135f7dc2f171633e4f60d8547c373)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.2...0.10.0)
|
||||||
|
|
||||||
## 0.9.2 (2021-02-13)
|
## 0.9.2 (2021-02-13)
|
||||||
|
|
||||||
* do not compile with unused locals vuejs/vetur#1063 | [73e0520](https://github.com/undergroundwires/privacy.sexy/commit/73e0520de70cdbaf0ecdc6e9be5e85f003fcfb79)
|
* do not compile with unused locals vuejs/vetur#1063 | [73e0520](https://github.com/undergroundwires/privacy.sexy/commit/73e0520de70cdbaf0ecdc6e9be5e85f003fcfb79)
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
- Proposing new features
|
- Proposing new features
|
||||||
- Becoming a maintainer
|
- Becoming a maintainer
|
||||||
|
|
||||||
## Pull Request Process
|
## Pull request process
|
||||||
|
|
||||||
- [GitHub flow](https://guides.github.com/introduction/flow/index.html) is used
|
- [GitHub flow](https://guides.github.com/introduction/flow/index.html) with [GitOps](./img/architecture/gitops.png) is used
|
||||||
- Your pull requests are actively welcomed.
|
- Your pull requests are actively welcomed.
|
||||||
- The steps:
|
- The steps:
|
||||||
1. Fork the repo and create your branch from master.
|
1. Fork the repo and create your branch from master.
|
||||||
@@ -25,4 +25,10 @@
|
|||||||
|
|
||||||
## 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](./LICENSE).
|
||||||
|
|
||||||
|
## Read more
|
||||||
|
|
||||||
|
- See [tests](./docs/tests.md) for testing
|
||||||
|
- See [extend script](./README.md#extend-scripts) for quick steps to extend scripts
|
||||||
|
- See [architecture overview](./README.md#architecture-overview) to deep dive into privacy.sexy codebase
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
- Online version at [https://privacy.sexy](https://privacy.sexy)
|
- Online version at [https://privacy.sexy](https://privacy.sexy)
|
||||||
- 💡 No need to run any compiled software on your computer.
|
- 💡 No need to run any compiled software on your computer.
|
||||||
- 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).
|
- Alternatively download offline version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.0/privacy.sexy-Setup-0.11.0.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.0/privacy.sexy-0.11.0.dmg) or [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.0/privacy.sexy-0.11.0.AppImage).
|
||||||
- 💡 Single click to execute your script.
|
- 💡 Single click to execute your script.
|
||||||
- ❗ Come back regularly to apply latest version for stronger privacy and security.
|
- ❗ Come back regularly to apply latest version for stronger privacy and security.
|
||||||
|
|
||||||
@@ -30,21 +30,25 @@
|
|||||||
- Have full visibility into what the tweaks do as you enable them
|
- Have full visibility into what the tweaks do as you enable them
|
||||||
- Ability to revert (undo) applied scripts
|
- Ability to revert (undo) applied scripts
|
||||||
- Everything is transparent: both application and its infrastructure are open-source and automated
|
- Everything is transparent: both application and its infrastructure are open-source and automated
|
||||||
- Easily extendable
|
- Easily extendable with [own powerful templating language](./docs/templating.md)
|
||||||
|
- Each script is independently executable without cross-dependencies
|
||||||
|
|
||||||
## Extend scripts
|
## Extend scripts
|
||||||
|
|
||||||
1. Fork the repository
|
- You can either [create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose)
|
||||||
2. Add more scripts in respective script collection in [collections](src/application/collections/) folder.
|
- Or send a PR:
|
||||||
- 📖 If you're unsure about the syntax you can refer to the [collection files | documentation](docs/collection-files.md).
|
1. Fork the repository
|
||||||
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
|
2. Add more scripts in respective script collection in [collections](src/application/collections/) folder.
|
||||||
3. Send a pull request 👌
|
- 📖 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
|
||||||
|
|
||||||
- Project setup: `npm install`
|
- Project setup: `npm install`
|
||||||
- Testing
|
- Testing
|
||||||
- Run unit tests: `npm run test:unit`
|
- Run unit tests: `npm run test:unit`
|
||||||
|
- Run integration tests: `npm run test:integration`
|
||||||
- Lint: `npm run lint`
|
- Lint: `npm run lint`
|
||||||
- **Desktop app**
|
- **Desktop app**
|
||||||
- Development: `npm run electron:serve`
|
- Development: `npm run electron:serve`
|
||||||
@@ -53,8 +57,8 @@
|
|||||||
- Development: `npm run serve` to compile & hot-reload for development.
|
- Development: `npm run serve` to compile & hot-reload for development.
|
||||||
- Production: `npm run build` to prepare files for distribution.
|
- Production: `npm run build` to prepare files for distribution.
|
||||||
- Or run using Docker:
|
- Or run using Docker:
|
||||||
1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.2 .`
|
1. Build: `docker build -t undergroundwires/privacy.sexy:0.11.0 .`
|
||||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.2 undergroundwires/privacy.sexy:0.9.2`
|
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.11.0 undergroundwires/privacy.sexy:0.11.0`
|
||||||
|
|
||||||
## Architecture overview
|
## Architecture overview
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,15 @@
|
|||||||
|
|
||||||
- It's mainly responsible for
|
- It's mainly responsible for
|
||||||
- creating and event based [application state](#application-state)
|
- creating and event based [application state](#application-state)
|
||||||
- parsing and compiling [application data](#application-data)
|
- [parsing](#parsing) and [compiling](#compiling) [application data](#application-data)
|
||||||
|
- Consumed by [presentation layer](./presentation.md)
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- [`/src/` **`application/`**](./../src/application/): Contains all application related code.
|
||||||
|
- [**`collections/`**](./../src/application/collections/): Holds [collection files](./collection-files.md)
|
||||||
|
- [**`Common/`**](./../src/application/Common/): Contains common functionality that is shared in application layer.
|
||||||
|
- `..`: other classes are categorized using folders-by-feature structure
|
||||||
|
|
||||||
## Application state
|
## Application state
|
||||||
|
|
||||||
@@ -14,9 +22,23 @@
|
|||||||
|
|
||||||
## Application data
|
## Application data
|
||||||
|
|
||||||
- Compiled to `Application` domain object.
|
- Compiled to [`Application`](./../src/domain/Application.ts) domain object.
|
||||||
- The scripts are defined and controlled in different data files per OS
|
- 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
|
- Enables [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming) and easier contributions
|
||||||
- Application data is defined in collection files and
|
- 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 [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.
|
- 📖 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.
|
||||||
|
|
||||||
|
## Parsing
|
||||||
|
|
||||||
|
- Application data is parsed to domain object [`Application.ts`](./../src/domain/Application.ts)
|
||||||
|
- Steps
|
||||||
|
1. (Compile time) Load application data from [collection yaml files](./../src/application/collections/) using webpack loader
|
||||||
|
2. (Runtime) Parse and compile application and make it available to presentation layer by [`ApplicationFactory.ts`](./../src/application/ApplicationFactory.ts)
|
||||||
|
|
||||||
|
### Compiling
|
||||||
|
|
||||||
|
- Parsing the application files includes compiling scripts using [collection file defined functions](./collection-files.md#function)
|
||||||
|
- To extend the syntax:
|
||||||
|
1. Add a new parser under [SyntaxParsers](./../src/application/Parser/Script/Compiler/Expressions/SyntaxParsers) where you can look at other parsers to understand more.
|
||||||
|
2. Register your in [CompositeExpressionParser](./../src/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser.ts)
|
||||||
|
|||||||
@@ -45,9 +45,11 @@
|
|||||||
### `Script`
|
### `Script`
|
||||||
|
|
||||||
- Script represents a single tweak.
|
- Script represents a single tweak.
|
||||||
- A script must include either:
|
- A script can be of two different types (just like [functions](#function)):
|
||||||
- A `code` and `revertCode`
|
1. Inline script; a script with an inline code
|
||||||
- Or `call` to call YAML-defined functions
|
- Must define `code` property and optionally `revertCode` but not `call`
|
||||||
|
2. Caller script; a script that calls other functions
|
||||||
|
- Must define `call` property but not `code` or `revertCode`
|
||||||
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
|
- 🙏 For any new script, please add `revertCode` and `docs` values if possible.
|
||||||
|
|
||||||
#### `Script` syntax
|
#### `Script` syntax
|
||||||
@@ -80,7 +82,7 @@
|
|||||||
### `FunctionCall`
|
### `FunctionCall`
|
||||||
|
|
||||||
- Describes a single call to a function by optionally providing values to its parameters.
|
- Describes a single call to a function by optionally providing values to its parameters.
|
||||||
- 👀 See [parameter substitution](#parameter-substitution) for an example usage
|
- 👀 See [parameter substitution](./templating.md#parameter-substitution) for an example usage
|
||||||
|
|
||||||
#### `FunctionCall` syntax
|
#### `FunctionCall` syntax
|
||||||
|
|
||||||
@@ -98,52 +100,18 @@
|
|||||||
appName: Microsoft.WindowsFeedbackHub
|
appName: Microsoft.WindowsFeedbackHub
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- 💡 [Expressions (templating)](./templating.md#expressions) can be used as parameter value
|
||||||
|
|
||||||
### `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](#expressions).
|
- Functions are templates compiled by privacy.sexy and uses special expression expressions.
|
||||||
- Functions can call other functions by defining `call` property instead of `code`
|
- A function can be of two different types (just like [scripts](#script)):
|
||||||
- 👀 See [parameter substitution](#parameter-substitution) for an example usage
|
1. Inline function: a function with an inline code.
|
||||||
|
- Must define `code` property and optionally `revertCode` but not `call`.
|
||||||
#### Expressions
|
2. Caller function: a function that calls other functions.
|
||||||
|
- Must define `call` property but not `code` or `revertCode`.
|
||||||
- Expressions are defined inside mustaches (double brackets, `{{` and `}}`)
|
- 👀 Read more on [Templating](./templating.md) for function expressions and [example usages](./templating.md#parameter-substitution).
|
||||||
|
|
||||||
##### Parameter substitution
|
|
||||||
|
|
||||||
A simple function example
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
function: EchoArgument
|
|
||||||
parameters: [ 'argument' ]
|
|
||||||
code: Hello {{ $argument }} !
|
|
||||||
```
|
|
||||||
|
|
||||||
It would print "Hello world" if it's called in a [script](#script) as following:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
script: Echo script
|
|
||||||
call:
|
|
||||||
function: EchoArgument
|
|
||||||
parameters:
|
|
||||||
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
|
||||||
|
|
||||||
@@ -152,24 +120,43 @@ A function can call other functions such as:
|
|||||||
- Convention is to use camelCase, and be verbs.
|
- Convention is to use camelCase, and be verbs.
|
||||||
- E.g. `uninstallStoreApp`
|
- E.g. `uninstallStoreApp`
|
||||||
- ❗ Function names must be unique
|
- ❗ Function names must be unique
|
||||||
- `parameters`: `[` *`string`* `, ... ]`
|
- `parameters`: `[` ***[`FunctionParameter`](#FunctionParameter)*** `, ... ]`
|
||||||
- Name of the parameters that the function has.
|
- List of parameters that function code refers to.
|
||||||
- Parameter values are provided by a [Script](#script) through a [FunctionCall](#FunctionCall)
|
- ❗ Must be defined to be able use in [`FunctionCall`](#functioncall) or [expressions (templating)](./templating.md#expressions)
|
||||||
- Parameter names must be defined to be used in [expressions](#expressions)
|
|
||||||
- ❗ Parameter names must be unique
|
|
||||||
`code`: *`string`* (**required** if `call` is undefined)
|
`code`: *`string`* (**required** if `call` is undefined)
|
||||||
- Batch file commands that will be executed
|
- Batch file commands that will be executed
|
||||||
|
- 💡 [Expressions (templating)](./templating.md#expressions) can be used in its value
|
||||||
- 💡 If defined, best practice to also define `revertCode`
|
- 💡 If defined, best practice to also define `revertCode`
|
||||||
- ❗ If not defined `call` must be defined
|
- ❗ 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`
|
||||||
|
- 💡 [Expressions (templating)](./templating.md#expressions) can be used in code
|
||||||
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
|
- `call`: ***[`FunctionCall`](#FunctionCall)*** | `[` ***[`FunctionCall`](#FunctionCall)*** `, ... ]` (may be **required**)
|
||||||
- A shared function or sequence of functions to call (called in order)
|
- A shared function or sequence of functions to call (called in order)
|
||||||
- The parameter values that are sent can use [expressions](#expressions)
|
- The parameter values that are sent can use [expressions (templating)](./templating.md#expressions)
|
||||||
- ❗ If not defined `code` must be defined
|
- ❗ If not defined `code` must be defined
|
||||||
|
|
||||||
|
### `FunctionParameter`
|
||||||
|
|
||||||
|
- Defines a parameter that function requires optionally or mandatory.
|
||||||
|
- Its arguments are provided by a [Script](#script) through a [FunctionCall](#FunctionCall).
|
||||||
|
|
||||||
|
#### `FunctionParameter` syntax
|
||||||
|
|
||||||
|
- `name`: *`string`* (**required**)
|
||||||
|
- Name of the parameters that the function has.
|
||||||
|
- Parameter names must be defined to be used in [expressions (templating)](./templating.md#expressions).
|
||||||
|
- ❗ Parameter names must be unique and include alphanumeric characters only.
|
||||||
|
- `optional`: *`boolean`* (default: `false`)
|
||||||
|
- Specifies whether the caller [Script](#script) must provide any value for the parameter.
|
||||||
|
- If set to `false` i.e. an argument value is not optional then it expects a non-empty value for the variable;
|
||||||
|
- Otherwise it throws.
|
||||||
|
- 💡 Set it to `true` if a parameter is used conditionally;
|
||||||
|
- Or else set it to `false` for verbosity or do not define it as default value is `false` anyway.
|
||||||
|
- 💡 Can be used in conjunction with [`with` expression](./templating.md#with).
|
||||||
|
|
||||||
### `ScriptingDefinition`
|
### `ScriptingDefinition`
|
||||||
|
|
||||||
- Defines global properties for scripting that's used throughout its parent [Collection](#collection).
|
- Defines global properties for scripting that's used throughout its parent [Collection](#collection).
|
||||||
@@ -180,7 +167,7 @@ A function can call other functions such as:
|
|||||||
- 📖 See [ScriptingLanguage.ts](./../src/domain/ScriptingLanguage.ts) enumeration for allowed values.
|
- 📖 See [ScriptingLanguage.ts](./../src/domain/ScriptingLanguage.ts) enumeration for allowed values.
|
||||||
- `startCode:` *`string`* (**required**)
|
- `startCode:` *`string`* (**required**)
|
||||||
- Code that'll be inserted on top of user created script.
|
- 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 }}!`
|
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
|
||||||
- `endCode:` *`string`* (**required**)
|
- `endCode:` *`string`* (**required**)
|
||||||
- Code that'll be inserted at the end of user created script.
|
- 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 }}!`
|
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
|
||||||
|
|||||||
@@ -4,6 +4,21 @@
|
|||||||
- Desktop application is created using [Electron](https://www.electronjs.org/).
|
- Desktop application is created using [Electron](https://www.electronjs.org/).
|
||||||
- Event driven as in components simply listens to events from the state and act accordingly.
|
- Event driven as in components simply listens to events from the state and act accordingly.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- [`/src/` **`presentation/`**](./../src/presentation/): Contains all presentation related code including Vue and Electron configurations
|
||||||
|
- [**`bootstrapping/`**](./../src/presentation/bootstrapping/): Registers Vue global objects including components and plugins.
|
||||||
|
- [**`components/`**](./../src/presentation/components/): Contains all Vue components and their helper classes.
|
||||||
|
- [**`Shared/`**](./../src/presentation/components/Shared): Contains Vue components and component helpers that are shared across other components.
|
||||||
|
- [**`styles/`**](./../src/presentation/styles/): Contains shared styles used throughout different components.
|
||||||
|
- [**`main.ts`**](./../src/presentation/main.ts): Application entry point that mounts and starts Vue application.
|
||||||
|
- [`electron/`](./../src/presentation/electron/): Electron configuration for the desktop application.
|
||||||
|
- [**`main.ts`**](./../src/presentation/main.ts): Main process of Electron, started as first thing when app starts.
|
||||||
|
- [**`/public/`**](./../public/): Contains static assets that will simply be copied and not go through webpack.
|
||||||
|
- [**`/vue.config.js`**](./../vue.config.js): Global Vue CLI configurations loaded by `@vue/cli-service`
|
||||||
|
- [**`/postcss.config.js`**](./../postcss.config.js): PostCSS configurations that are used by Vue CLI internally
|
||||||
|
- [**`/babel.config.js`**](./../babel.config.js): Babel configurations for polyfills used by `@vue/cli-plugin-babel`
|
||||||
|
|
||||||
## Application data
|
## Application data
|
||||||
|
|
||||||
- Components and should use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain.
|
- Components and should use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain.
|
||||||
@@ -16,9 +31,22 @@
|
|||||||
|
|
||||||
- Stateful components mutate or/and react to state changes in [ApplicationContext](./../src/application/Context/ApplicationContext.ts).
|
- 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`
|
- 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)
|
- Stateful components that depends on the collection state such as user selection, search queries and more extends [`StatefulVue`](./../src/presentation/components/Shared/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)
|
- The single source of truth is a singleton of the state created and made available to presentation layer by [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts)
|
||||||
- `StatefulVue` includes abstract `handleCollectionState` that is fired once the component is loaded and also each time [collection](./collection-files.md) is changed.
|
- `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.
|
- 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
|
- 💡 `events` in base class [`StatefulVue`](./../src/presentation/components/Shared/StatefulVue.ts) makes lifecycling easier
|
||||||
- 📖 See [Application state | Application layer](./presentation.md#application-state) where the state is implemented using using state pattern.
|
- 📖 See [Application state | Application layer](./presentation.md#application-state) where the state is implemented using using state pattern.
|
||||||
|
|
||||||
|
## Modals
|
||||||
|
|
||||||
|
- [Dialog.vue](./../src/presentation/components/Shared/Dialog.vue) is a shared component that can be used to show modal windows
|
||||||
|
- Simply wrap the content inside of its slot and call `.show()` method on its reference.
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<Dialog ref="testDialog">
|
||||||
|
<div>Hello world</div>
|
||||||
|
</Dialog>
|
||||||
|
<div @click="$refs.testDialog.show()">Show dialog</div>
|
||||||
|
```
|
||||||
|
|||||||
88
docs/templating.md
Normal file
88
docs/templating.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Templating
|
||||||
|
|
||||||
|
## Benefits of templating
|
||||||
|
|
||||||
|
- Generating scripts by sharing code to increase best-practice usage and maintainability.
|
||||||
|
- Creating self-contained scripts without depending on each other that can be easily shared.
|
||||||
|
- Use of pipes for writing cleaner code and letting pipes do dirty work.
|
||||||
|
|
||||||
|
## Expressions
|
||||||
|
|
||||||
|
- Expressions in the language are defined inside mustaches (double brackets, `{{` and `}}`).
|
||||||
|
- Expression syntax is inspired mainly by [Go Templates](https://pkg.go.dev/text/template).
|
||||||
|
- Expressions are used in and enabled by functions where they can be used.
|
||||||
|
- In script definition parts of a function, see [`Function`](./collection-files.md#Function).
|
||||||
|
- When doing a call as argument values, see [`FunctionCall`](./collection-files.md#Function).
|
||||||
|
|
||||||
|
### Parameter substitution
|
||||||
|
|
||||||
|
A simple function example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
function: EchoArgument
|
||||||
|
parameters:
|
||||||
|
- name: 'argument'
|
||||||
|
code: Hello {{ $argument }} !
|
||||||
|
```
|
||||||
|
|
||||||
|
It would print "Hello world" if it's called in a [script](./collection-files.md#script) as following:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
script: Echo script
|
||||||
|
call:
|
||||||
|
function: EchoArgument
|
||||||
|
parameters:
|
||||||
|
argument: World
|
||||||
|
```
|
||||||
|
|
||||||
|
A function can call other functions such as:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
-
|
||||||
|
function: CallerFunction
|
||||||
|
parameters:
|
||||||
|
- name: 'value'
|
||||||
|
call:
|
||||||
|
function: EchoArgument
|
||||||
|
parameters:
|
||||||
|
argument: {{ $value }}
|
||||||
|
-
|
||||||
|
function: EchoArgument
|
||||||
|
parameters:
|
||||||
|
- name: 'argument'
|
||||||
|
code: Hello {{ $argument }} !
|
||||||
|
```
|
||||||
|
|
||||||
|
### with
|
||||||
|
|
||||||
|
- Skips the block if the variable is absent or empty.
|
||||||
|
- Binds its context (`.`) value of provided argument for the parameter if provided one.
|
||||||
|
- A block is defined as `{{ with $parameterName }} Parameter value is {{ . }} here {{ end }}`.
|
||||||
|
- The parameters used for `with` condition should be declared as optional, otherwise `with` block becomes redundant.
|
||||||
|
- Example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
function: FunctionThatOutputsConditionally
|
||||||
|
parameters:
|
||||||
|
- name: 'argument'
|
||||||
|
optional: true
|
||||||
|
code: |-
|
||||||
|
{{ with $argument }}
|
||||||
|
Value is: {{ . }}
|
||||||
|
{{ end }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pipes
|
||||||
|
|
||||||
|
- Pipes are set of functions available for handling text in privacy.sexy.
|
||||||
|
- Allows stacking actions one after another also known as "chaining".
|
||||||
|
- Just like [Unix pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)), the concept is simple: each pipeline's output becomes the input of the following pipe.
|
||||||
|
- Pipes are provided and defined by the compiler and consumed by collection files.
|
||||||
|
- Pipes can be combined with [parameter substitution](#parameter-substitution) and [with](#with).
|
||||||
|
- ❗ Pipe names must be camelCase without any space or special characters.
|
||||||
|
- **Existing pipes**
|
||||||
|
- `inlinePowerShell`: Converts a multi-lined PowerShell script to a single line.
|
||||||
|
- `escapeDoubleQuotes`: Escapes `"` characters to be used inside double quotes (`"`)
|
||||||
|
- **Example usages**
|
||||||
|
- `{{ with $code }} echo "{{ . | inlinePowerShell }}" {{ end }}`
|
||||||
|
- `{{ with $code }} echo "{{ . | inlinePowerShell | escapeDoubleQuotes }}" {{ end }}`
|
||||||
@@ -1,16 +1,24 @@
|
|||||||
# Unit tests
|
# Tests
|
||||||
|
|
||||||
- Unit tests are defined in [`./tests`](./../tests)
|
- There are two different types of tests executed:
|
||||||
|
1. [Unit tests](#unit-tests)
|
||||||
|
2. [Integration tests](#integration-tests)
|
||||||
|
- 💡 You can use path/module alias `@/tests` in import statements.
|
||||||
|
|
||||||
|
## Unit tests
|
||||||
|
|
||||||
|
- Tests each component in isolation
|
||||||
|
- Defined in [`./tests/unit`](./../tests/unit)
|
||||||
- They follow same folder structure as [`./src`](./../src)
|
- They follow same folder structure as [`./src`](./../src)
|
||||||
|
|
||||||
## Naming
|
### Naming
|
||||||
|
|
||||||
- Each test suite first describe the system under test
|
- Each test suite first describe the system under test
|
||||||
- E.g. tests for class `Application` is categorized under `Application`
|
- E.g. tests for class `Application` is categorized under `Application`
|
||||||
- Tests for specific methods are categorized under method name (if applicable)
|
- Tests for specific methods are categorized under method name (if applicable)
|
||||||
- E.g. test for `run()` is categorized under `run`
|
- E.g. test for `run()` is categorized under `run`
|
||||||
|
|
||||||
## Act, arrange, assert
|
### Act, arrange, assert
|
||||||
|
|
||||||
- Tests use act, arrange and assert (AAA) pattern when applicable
|
- Tests use act, arrange and assert (AAA) pattern when applicable
|
||||||
- **Arrange**
|
- **Arrange**
|
||||||
@@ -23,7 +31,13 @@
|
|||||||
- Should elicit some sort of response
|
- Should elicit some sort of response
|
||||||
- Starts with comment line `// assert`
|
- Starts with comment line `// assert`
|
||||||
|
|
||||||
## Stubs
|
### Stubs
|
||||||
|
|
||||||
- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs)
|
- Stubs are defined in [`./tests/stubs`](./../tests/unit/stubs)
|
||||||
- They implement dummy behavior to be functional
|
- They implement dummy behavior to be functional
|
||||||
|
|
||||||
|
## Integration tests
|
||||||
|
|
||||||
|
- Tests functionality of a component in combination with others (not isolated)
|
||||||
|
- Ensure dependencies to third parties work as expected
|
||||||
|
- Defined in [`./tests/integration`](./../tests/integration)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 483 KiB After Width: | Height: | Size: 579 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 98 KiB |
32697
package-lock.json
generated
32697
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
77
package.json
77
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.9.2",
|
"version": "0.11.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
|
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
|
||||||
"author": "undergroundwires",
|
"author": "undergroundwires",
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
"test:unit": "vue-cli-service test:unit",
|
"test:unit": "vue-cli-service test:unit",
|
||||||
|
"test:integration": "vue-cli-service test:unit \"tests/integration/**/*.spec.ts\"",
|
||||||
"lint": "npm run lint:vue && npm run lint:yaml && npm run lint:md && npm run lint:md:relative-urls && npm run lint:md:consistency",
|
"lint": "npm run lint:vue && npm run lint:yaml && npm run lint:md && npm run lint:md:relative-urls && npm run lint:md:consistency",
|
||||||
"electron:build": "vue-cli-service electron:build",
|
"electron:build": "vue-cli-service electron:build",
|
||||||
"electron:serve": "vue-cli-service electron:serve",
|
"electron:serve": "vue-cli-service electron:serve",
|
||||||
@@ -21,49 +22,53 @@
|
|||||||
},
|
},
|
||||||
"main": "background.js",
|
"main": "background.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
"@fortawesome/fontawesome-svg-core": "^1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.1",
|
"@fortawesome/free-brands-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-regular-svg-icons": "^5.15.1",
|
"@fortawesome/free-regular-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
"@fortawesome/free-solid-svg-icons": "^5.15.4",
|
||||||
"@fortawesome/vue-fontawesome": "^2.0.2",
|
"@fortawesome/vue-fontawesome": "^2.0.6",
|
||||||
"ace-builds": "^1.4.12",
|
"@juggle/resize-observer": "^3.3.1",
|
||||||
"core-js": "^3.6.5",
|
"ace-builds": "^1.4.13",
|
||||||
|
"core-js": "^3.18.3",
|
||||||
|
"cross-fetch": "^3.1.4",
|
||||||
|
"electron-progressbar": "^2.0.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"inversify": "^5.0.5",
|
"install": "^0.13.0",
|
||||||
"liquor-tree": "^0.2.70",
|
"liquor-tree": "^0.2.70",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"npm": "^8.1.1",
|
||||||
"v-tooltip": "2.0.2",
|
"v-tooltip": "2.1.3",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.14",
|
||||||
"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.1",
|
||||||
"vue-property-decorator": "^9.1.2"
|
"vue-property-decorator": "^9.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/ace": "0.0.44",
|
"@types/ace": "0.0.47",
|
||||||
"@types/chai": "^4.2.14",
|
"@types/chai": "^4.2.22",
|
||||||
"@types/file-saver": "^2.0.1",
|
"@types/file-saver": "^2.0.3",
|
||||||
"@types/mocha": "^8.2.0",
|
"@types/mocha": "^9.0.0",
|
||||||
"@vue/cli-plugin-babel": "^4.5.10",
|
"@vue/cli-plugin-babel": "^4.5.14",
|
||||||
"@vue/cli-plugin-typescript": "^4.5.9",
|
"@vue/cli-plugin-typescript": "^4.5.14",
|
||||||
"@vue/cli-plugin-unit-mocha": "^4.5.9",
|
"@vue/cli-plugin-unit-mocha": "^4.5.14",
|
||||||
"@vue/cli-service": "^4.5.9",
|
"@vue/cli-service": "^4.5.14",
|
||||||
"@vue/test-utils": "1.1.2",
|
"@vue/test-utils": "1.2.2",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.3.4",
|
||||||
"electron": "^11.1.0",
|
"electron": "^15.3.0",
|
||||||
"electron-devtools-installer": "^3.1.1",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-log": "^4.3.1",
|
"electron-log": "^4.4.1",
|
||||||
"electron-updater": "^4.3.5",
|
"electron-updater": "^4.3.9",
|
||||||
"js-yaml-loader": "^1.2.2",
|
"js-yaml-loader": "^1.2.2",
|
||||||
"markdownlint-cli": "^0.26.0",
|
"markdownlint-cli": "^0.29.0",
|
||||||
"remark-cli": "^9.0.0",
|
"remark-cli": "^10.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": "^5.1.0",
|
||||||
"remark-validate-links": "^10.0.2",
|
"remark-validate-links": "^11.0.1",
|
||||||
"sass": "^1.30.0",
|
"sass": "^1.43.3",
|
||||||
"sass-loader": "^10.1.0",
|
"sass-loader": "10.2.0",
|
||||||
"typescript": "^4.1.3",
|
"tslib": "^2.3.1",
|
||||||
"vue-cli-plugin-electron-builder": "^2.0.0-rc.5",
|
"typescript": "^4.4.4",
|
||||||
"vue-template-compiler": "^2.6.12",
|
"vue-cli-plugin-electron-builder": "^2.1.1",
|
||||||
|
"vue-template-compiler": "^2.6.14",
|
||||||
"yaml-lint": "^1.2.4"
|
"yaml-lint": "^1.2.4"
|
||||||
},
|
},
|
||||||
"homepage": "https://privacy.sexy",
|
"homepage": "https://privacy.sexy",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class ApplicationFactory implements IApplicationFactory {
|
|||||||
}
|
}
|
||||||
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
|
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
|
||||||
}
|
}
|
||||||
public getAppAsync(): Promise<IApplication> {
|
public getApp(): Promise<IApplication> {
|
||||||
return this.getter.getValueAsync();
|
return this.getter.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/application/Common/Array.ts
Normal file
21
src/application/Common/Array.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Compares to Array<T> objects for equality, ignoring order
|
||||||
|
export function scrambledEqual<T>(array1: readonly T[], array2: readonly T[]) {
|
||||||
|
if (!array1) { throw new Error('undefined first array'); }
|
||||||
|
if (!array2) { throw new Error('undefined second array'); }
|
||||||
|
const sortedArray1 = sort(array1);
|
||||||
|
const sortedArray2 = sort(array2);
|
||||||
|
return sequenceEqual(sortedArray1, sortedArray2);
|
||||||
|
function sort(array: readonly T[]) {
|
||||||
|
return array.slice().sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compares to Array<T> objects for equality in same order
|
||||||
|
export function sequenceEqual<T>(array1: readonly T[], array2: readonly T[]) {
|
||||||
|
if (!array1) { throw new Error('undefined first array'); }
|
||||||
|
if (!array2) { throw new Error('undefined second array'); }
|
||||||
|
if (array1.length !== array2.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return array1.every((val, index) => val === array2[index]);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611
|
// Because we cannot do "T extends enum" 😞 https://github.com/microsoft/TypeScript/issues/30611
|
||||||
type EnumType = number | string;
|
export type EnumType = number | string;
|
||||||
type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
|
export type EnumVariable<T extends EnumType, TEnumValue extends EnumType> = { [key in T]: TEnumValue };
|
||||||
|
|
||||||
export interface IEnumParser<TEnum> {
|
export interface IEnumParser<TEnum> {
|
||||||
parseEnum(value: string, propertyName: string): TEnum;
|
parseEnum(value: string, propertyName: string): TEnum;
|
||||||
@@ -41,3 +41,14 @@ export function getEnumValues<T extends EnumType, TEnumValue extends EnumType>(
|
|||||||
return getEnumNames(enumVariable)
|
return getEnumNames(enumVariable)
|
||||||
.map((level) => enumVariable[level]) as TEnumValue[];
|
.map((level) => enumVariable[level]) as TEnumValue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function assertInRange<T extends EnumType, TEnumValue extends EnumType>(
|
||||||
|
value: TEnumValue,
|
||||||
|
enumVariable: EnumVariable<T, TEnumValue>) {
|
||||||
|
if (value === undefined) {
|
||||||
|
throw new Error('undefined enum value');
|
||||||
|
}
|
||||||
|
if (!(value in enumVariable)) {
|
||||||
|
throw new RangeError(`enum value "${value}" is out of range`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
|
|
||||||
|
export interface IScriptingLanguageFactory<T> {
|
||||||
|
create(language: ScriptingLanguage): T;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
|
import { IScriptingLanguageFactory } from './IScriptingLanguageFactory';
|
||||||
|
import { assertInRange } from '@/application/Common/Enum';
|
||||||
|
|
||||||
|
type Getter<T> = () => T;
|
||||||
|
|
||||||
|
export abstract class ScriptingLanguageFactory<T> implements IScriptingLanguageFactory<T> {
|
||||||
|
private readonly getters = new Map<ScriptingLanguage, Getter<T>>();
|
||||||
|
|
||||||
|
public create(language: ScriptingLanguage): T {
|
||||||
|
assertInRange(language, ScriptingLanguage);
|
||||||
|
if (!this.getters.has(language)) {
|
||||||
|
throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
|
||||||
|
}
|
||||||
|
const getter = this.getters.get(language);
|
||||||
|
const instance = getter();
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected registerGetter(language: ScriptingLanguage, getter: Getter<T>) {
|
||||||
|
assertInRange(language, ScriptingLanguage);
|
||||||
|
if (!getter) {
|
||||||
|
throw new Error('undefined getter');
|
||||||
|
}
|
||||||
|
if (this.getters.has(language)) {
|
||||||
|
throw new Error(`${ScriptingLanguage[language]} is already registered`);
|
||||||
|
}
|
||||||
|
this.getters.set(language, getter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { IApplication } from '@/domain/IApplication';
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
import { EventSource } from '@/infrastructure/Events/EventSource';
|
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
|
import { assertInRange } from '@/application/Common/Enum';
|
||||||
|
|
||||||
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
|
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ export class ApplicationContext implements IApplicationContext {
|
|||||||
public readonly app: IApplication,
|
public readonly app: IApplication,
|
||||||
initialContext: OperatingSystem) {
|
initialContext: OperatingSystem) {
|
||||||
validateApp(app);
|
validateApp(app);
|
||||||
validateOs(initialContext);
|
assertInRange(initialContext, OperatingSystem);
|
||||||
this.states = initializeStates(app);
|
this.states = initializeStates(app);
|
||||||
this.changeContext(initialContext);
|
this.changeContext(initialContext);
|
||||||
}
|
}
|
||||||
@@ -50,18 +51,6 @@ function validateApp(app: IApplication) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
function initializeStates(app: IApplication): StateMachine {
|
||||||
const machine = new Map<OperatingSystem, ICategoryCollectionState>();
|
const machine = new Map<OperatingSystem, ICategoryCollectionState>();
|
||||||
for (const collection of app.collections) {
|
for (const collection of app.collections) {
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import { IEnvironment } from '../Environment/IEnvironment';
|
|||||||
import { IApplicationFactory } from '../IApplicationFactory';
|
import { IApplicationFactory } from '../IApplicationFactory';
|
||||||
import { ApplicationFactory } from '../ApplicationFactory';
|
import { ApplicationFactory } from '../ApplicationFactory';
|
||||||
|
|
||||||
export async function buildContextAsync(
|
export async function buildContext(
|
||||||
factory: IApplicationFactory = ApplicationFactory.Current,
|
factory: IApplicationFactory = ApplicationFactory.Current,
|
||||||
environment = Environment.CurrentEnvironment): Promise<IApplicationContext> {
|
environment = Environment.CurrentEnvironment): Promise<IApplicationContext> {
|
||||||
if (!factory) { throw new Error('undefined factory'); }
|
if (!factory) { throw new Error('undefined factory'); }
|
||||||
if (!environment) { throw new Error('undefined environment'); }
|
if (!environment) { throw new Error('undefined environment'); }
|
||||||
const app = await factory.getAppAsync();
|
const app = await factory.getApp();
|
||||||
const os = getInitialOs(app, environment);
|
const os = getInitialOs(app, environment);
|
||||||
return new ApplicationContext(app, os);
|
return new ApplicationContext(app, os);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
|
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
|
||||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
import { ICodeBuilder } from './ICodeBuilder';
|
import { ICodeBuilder } from './ICodeBuilder';
|
||||||
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
|
|
||||||
import { BatchBuilder } from './Languages/BatchBuilder';
|
import { BatchBuilder } from './Languages/BatchBuilder';
|
||||||
import { ShellBuilder } from './Languages/ShellBuilder';
|
import { ShellBuilder } from './Languages/ShellBuilder';
|
||||||
|
import { ICodeBuilderFactory } from './ICodeBuilderFactory';
|
||||||
|
|
||||||
export class CodeBuilderFactory implements ICodeBuilderFactory {
|
export class CodeBuilderFactory extends ScriptingLanguageFactory<ICodeBuilder> implements ICodeBuilderFactory {
|
||||||
public create(language: ScriptingLanguage): ICodeBuilder {
|
constructor() {
|
||||||
switch (language) {
|
super();
|
||||||
case ScriptingLanguage.shellscript: return new ShellBuilder();
|
this.registerGetter(ScriptingLanguage.shellscript, () => new ShellBuilder());
|
||||||
case ScriptingLanguage.batchfile: return new BatchBuilder();
|
this.registerGetter(ScriptingLanguage.batchfile, () => new BatchBuilder());
|
||||||
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
|
||||||
import { ICodeBuilder } from './ICodeBuilder';
|
import { ICodeBuilder } from './ICodeBuilder';
|
||||||
|
import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
|
||||||
|
|
||||||
export interface ICodeBuilderFactory {
|
export interface ICodeBuilderFactory extends IScriptingLanguageFactory<ICodeBuilder> {
|
||||||
create(language: ScriptingLanguage): ICodeBuilder;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
|||||||
export interface IUserSelection {
|
export interface IUserSelection {
|
||||||
readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
|
readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
|
||||||
readonly selectedScripts: ReadonlyArray<SelectedScript>;
|
readonly selectedScripts: ReadonlyArray<SelectedScript>;
|
||||||
readonly totalSelected: number;
|
|
||||||
areAllSelected(category: ICategory): boolean;
|
areAllSelected(category: ICategory): boolean;
|
||||||
isAnySelected(category: ICategory): boolean;
|
isAnySelected(category: ICategory): boolean;
|
||||||
removeAllInCategory(categoryId: number): void;
|
removeAllInCategory(categoryId: number): void;
|
||||||
|
|||||||
@@ -101,10 +101,6 @@ export class UserSelection implements IUserSelection {
|
|||||||
return this.scripts.getItems();
|
return this.scripts.getItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get totalSelected(): number {
|
|
||||||
return this.scripts.getItems().length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public selectAll(): void {
|
public selectAll(): void {
|
||||||
for (const script of this.collection.getAllScripts()) {
|
for (const script of this.collection.getAllScripts()) {
|
||||||
if (!this.scripts.exists(script.id)) {
|
if (!this.scripts.exists(script.id)) {
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ import { IBrowserOsDetector } from './IBrowserOsDetector';
|
|||||||
|
|
||||||
export class BrowserOsDetector implements IBrowserOsDetector {
|
export class BrowserOsDetector implements IBrowserOsDetector {
|
||||||
private readonly detectors = BrowserDetectors;
|
private readonly detectors = BrowserDetectors;
|
||||||
public detect(userAgent: string): OperatingSystem {
|
public detect(userAgent: string): OperatingSystem | undefined {
|
||||||
if (!userAgent) {
|
if (!userAgent) {
|
||||||
return OperatingSystem.Unknown;
|
return undefined;
|
||||||
}
|
}
|
||||||
for (const detector of this.detectors) {
|
for (const detector of this.detectors) {
|
||||||
const os = detector.detect(userAgent);
|
const os = detector.detect(userAgent);
|
||||||
if (os !== OperatingSystem.Unknown) {
|
if (os !== undefined) {
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return OperatingSystem.Unknown;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ export class DetectorBuilder {
|
|||||||
throw new Error('User agent is null or undefined');
|
throw new Error('User agent is null or undefined');
|
||||||
}
|
}
|
||||||
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
|
if (this.existingPartsInUserAgent.some((part) => !userAgent.includes(part))) {
|
||||||
return OperatingSystem.Unknown;
|
return undefined;
|
||||||
}
|
}
|
||||||
if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) {
|
if (this.notExistingPartsInUserAgent.some((part) => userAgent.includes(part))) {
|
||||||
return OperatingSystem.Unknown;
|
return undefined;
|
||||||
}
|
}
|
||||||
return this.os;
|
return this.os;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
|
||||||
export interface IBrowserOsDetector {
|
export interface IBrowserOsDetector {
|
||||||
detect(userAgent: string): OperatingSystem;
|
detect(userAgent: string): OperatingSystem | undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function getProcessPlatform(variables: IEnvironmentVariables): string {
|
|||||||
return variables.process.platform;
|
return variables.process.platform;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDesktopOsType(processPlatform: string): OperatingSystem {
|
function getDesktopOsType(processPlatform: string): OperatingSystem | undefined {
|
||||||
// https://nodejs.org/api/process.html#process_process_platform
|
// https://nodejs.org/api/process.html#process_process_platform
|
||||||
if (processPlatform === 'darwin') {
|
if (processPlatform === 'darwin') {
|
||||||
return OperatingSystem.macOS;
|
return OperatingSystem.macOS;
|
||||||
@@ -53,7 +53,7 @@ function getDesktopOsType(processPlatform: string): OperatingSystem {
|
|||||||
} else if (processPlatform === 'linux') {
|
} else if (processPlatform === 'linux') {
|
||||||
return OperatingSystem.Linux;
|
return OperatingSystem.Linux;
|
||||||
}
|
}
|
||||||
return OperatingSystem.Unknown;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDesktop(variables: IEnvironmentVariables): boolean {
|
function isDesktop(variables: IEnvironmentVariables): boolean {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
export interface IApplicationFactory {
|
export interface IApplicationFactory {
|
||||||
getAppAsync(): Promise<IApplication>;
|
getApp(): Promise<IApplication>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,20 @@ import { Category } from '@/domain/Category';
|
|||||||
import { CollectionData } from 'js-yaml-loader!@/*';
|
import { CollectionData } from 'js-yaml-loader!@/*';
|
||||||
import { parseCategory } from './CategoryParser';
|
import { parseCategory } from './CategoryParser';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { parseScriptingDefinition } from './ScriptingDefinitionParser';
|
|
||||||
import { createEnumParser } from '../Common/Enum';
|
import { createEnumParser } from '../Common/Enum';
|
||||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
import { CategoryCollection } from '@/domain/CategoryCollection';
|
import { CategoryCollection } from '@/domain/CategoryCollection';
|
||||||
import { IProjectInformation } from '@/domain/IProjectInformation';
|
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||||
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
|
import { CategoryCollectionParseContext } from './Script/CategoryCollectionParseContext';
|
||||||
|
import { ScriptingDefinitionParser } from './ScriptingDefinition/ScriptingDefinitionParser';
|
||||||
|
|
||||||
export function parseCategoryCollection(
|
export function parseCategoryCollection(
|
||||||
content: CollectionData,
|
content: CollectionData,
|
||||||
info: IProjectInformation,
|
info: IProjectInformation,
|
||||||
osParser = createEnumParser(OperatingSystem)): ICategoryCollection {
|
osParser = createEnumParser(OperatingSystem)): ICategoryCollection {
|
||||||
validate(content);
|
validate(content);
|
||||||
const scripting = parseScriptingDefinition(content.scripting, info);
|
const scripting = new ScriptingDefinitionParser()
|
||||||
|
.parse(content.scripting, info);
|
||||||
const context = new CategoryCollectionParseContext(content.functions, scripting);
|
const context = new CategoryCollectionParseContext(content.functions, scripting);
|
||||||
const categories = new Array<Category>();
|
const categories = new Array<Category>();
|
||||||
for (const action of content.actions) {
|
for (const action of content.actions) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
import { FunctionData } from 'js-yaml-loader!*';
|
import { FunctionData } from 'js-yaml-loader!@/*';
|
||||||
import { IScriptCompiler } from './Compiler/IScriptCompiler';
|
import { IScriptCompiler } from './Compiler/IScriptCompiler';
|
||||||
import { ScriptCompiler } from './Compiler/ScriptCompiler';
|
import { ScriptCompiler } from './Compiler/ScriptCompiler';
|
||||||
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
|
import { ICategoryCollectionParseContext } from './ICategoryCollectionParseContext';
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { ExpressionPosition } from './ExpressionPosition';
|
||||||
|
import { IExpression } from './IExpression';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '../../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from '../../Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
||||||
|
import { FunctionCallArgumentCollection } from '../../Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
|
import { IExpressionEvaluationContext } from './ExpressionEvaluationContext';
|
||||||
|
import { ExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||||
|
|
||||||
|
export type ExpressionEvaluator = (context: IExpressionEvaluationContext) => string;
|
||||||
|
export class Expression implements IExpression {
|
||||||
|
constructor(
|
||||||
|
public readonly position: ExpressionPosition,
|
||||||
|
public readonly evaluator: ExpressionEvaluator,
|
||||||
|
public readonly parameters: IReadOnlyFunctionParameterCollection = new FunctionParameterCollection()) {
|
||||||
|
if (!position) {
|
||||||
|
throw new Error('undefined position');
|
||||||
|
}
|
||||||
|
if (!evaluator) {
|
||||||
|
throw new Error('undefined evaluator');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public evaluate(context: IExpressionEvaluationContext): string {
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('undefined context');
|
||||||
|
}
|
||||||
|
validateThatAllRequiredParametersAreSatisfied(this.parameters, context.args);
|
||||||
|
const args = filterUnusedArguments(this.parameters, context.args);
|
||||||
|
context = new ExpressionEvaluationContext(args, context.pipelineCompiler);
|
||||||
|
return this.evaluator(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateThatAllRequiredParametersAreSatisfied(
|
||||||
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
) {
|
||||||
|
const requiredParameterNames = parameters
|
||||||
|
.all
|
||||||
|
.filter((parameter) => !parameter.isOptional)
|
||||||
|
.map((parameter) => parameter.name);
|
||||||
|
const missingParameterNames = requiredParameterNames
|
||||||
|
.filter((parameterName) => !args.hasArgument(parameterName));
|
||||||
|
if (missingParameterNames.length) {
|
||||||
|
throw new Error(
|
||||||
|
`argument values are provided for required parameters: "${missingParameterNames.join('", "')}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterUnusedArguments(
|
||||||
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
|
allFunctionArgs: IReadOnlyFunctionCallArgumentCollection): IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
const specificCallArgs = new FunctionCallArgumentCollection();
|
||||||
|
for (const parameter of parameters.all) {
|
||||||
|
if (parameter.isOptional && !allFunctionArgs.hasArgument(parameter.name)) {
|
||||||
|
continue; // Optional parameter is not necessarily provided
|
||||||
|
}
|
||||||
|
const arg = allFunctionArgs.getArgument(parameter.name);
|
||||||
|
specificCallArgs.addArgument(arg);
|
||||||
|
}
|
||||||
|
return specificCallArgs;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '../../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IPipelineCompiler } from '../Pipes/IPipelineCompiler';
|
||||||
|
import { PipelineCompiler } from '../Pipes/PipelineCompiler';
|
||||||
|
|
||||||
|
export interface IExpressionEvaluationContext {
|
||||||
|
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
||||||
|
readonly pipelineCompiler: IPipelineCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExpressionEvaluationContext implements IExpressionEvaluationContext {
|
||||||
|
constructor(
|
||||||
|
public readonly args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
public readonly pipelineCompiler: IPipelineCompiler = new PipelineCompiler()) {
|
||||||
|
if (!args) {
|
||||||
|
throw new Error('undefined args, send empty collection instead');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export class ExpressionPosition {
|
||||||
|
constructor(
|
||||||
|
public readonly start: number,
|
||||||
|
public readonly end: number) {
|
||||||
|
if (start === end) {
|
||||||
|
throw new Error(`no length (start = end = ${start})`);
|
||||||
|
}
|
||||||
|
if (start > end) {
|
||||||
|
throw Error(`start (${start}) after end (${end})`);
|
||||||
|
}
|
||||||
|
if (start < 0) {
|
||||||
|
throw Error(`negative start position: ${start}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { ExpressionPosition } from './ExpressionPosition';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from '../../Function/Parameter/IFunctionParameterCollection';
|
||||||
|
import { IExpressionEvaluationContext } from './ExpressionEvaluationContext';
|
||||||
|
|
||||||
|
export interface IExpression {
|
||||||
|
readonly position: ExpressionPosition;
|
||||||
|
readonly parameters: IReadOnlyFunctionParameterCollection;
|
||||||
|
evaluate(context: IExpressionEvaluationContext): string;
|
||||||
|
}
|
||||||
@@ -1,31 +1,75 @@
|
|||||||
import { IExpressionsCompiler, ParameterValueDictionary } from './IExpressionsCompiler';
|
import { IExpressionsCompiler } from './IExpressionsCompiler';
|
||||||
import { generateIlCode, IILCode } from './ILCode';
|
import { IExpression } from './Expression/IExpression';
|
||||||
|
import { IExpressionParser } from './Parser/IExpressionParser';
|
||||||
|
import { CompositeExpressionParser } from './Parser/CompositeExpressionParser';
|
||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { ExpressionEvaluationContext } from './Expression/ExpressionEvaluationContext';
|
||||||
|
import { IExpressionEvaluationContext } from '@/application/Parser/Script/Compiler/Expressions/Expression/ExpressionEvaluationContext';
|
||||||
|
|
||||||
export class ExpressionsCompiler implements IExpressionsCompiler {
|
export class ExpressionsCompiler implements IExpressionsCompiler {
|
||||||
public static readonly instance: IExpressionsCompiler = new ExpressionsCompiler();
|
public constructor(
|
||||||
protected constructor() { }
|
private readonly extractor: IExpressionParser = new CompositeExpressionParser()) { }
|
||||||
public compileExpressions(code: string, parameters?: ParameterValueDictionary): string {
|
public compileExpressions(
|
||||||
let intermediateCode = generateIlCode(code);
|
code: string,
|
||||||
intermediateCode = substituteParameters(intermediateCode, parameters);
|
args: IReadOnlyFunctionCallArgumentCollection): string {
|
||||||
return intermediateCode.compile();
|
if (!args) {
|
||||||
|
throw new Error('undefined args, send empty collection instead');
|
||||||
|
}
|
||||||
|
const expressions = this.extractor.findExpressions(code);
|
||||||
|
ensureParamsUsedInCodeHasArgsProvided(expressions, args);
|
||||||
|
const context = new ExpressionEvaluationContext(args);
|
||||||
|
const compiledCode = compileExpressions(expressions, code, context);
|
||||||
|
return compiledCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function substituteParameters(intermediateCode: IILCode, parameters: ParameterValueDictionary): IILCode {
|
function compileExpressions(
|
||||||
const parameterNames = intermediateCode.getUniqueParameterNames();
|
expressions: readonly IExpression[],
|
||||||
ensureValuesProvided(parameterNames, parameters);
|
code: string,
|
||||||
for (const parameterName of parameterNames) {
|
context: IExpressionEvaluationContext) {
|
||||||
const parameterValue = parameters[parameterName];
|
let compiledCode = '';
|
||||||
intermediateCode = intermediateCode.substituteParameter(parameterName, parameterValue);
|
const sortedExpressions = expressions
|
||||||
|
.slice() // copy the array to not mutate the parameter
|
||||||
|
.sort((a, b) => b.position.start - a.position.start);
|
||||||
|
let index = 0;
|
||||||
|
while (index !== code.length) {
|
||||||
|
const nextExpression = sortedExpressions.pop();
|
||||||
|
if (nextExpression) {
|
||||||
|
compiledCode += code.substring(index, nextExpression.position.start);
|
||||||
|
const expressionCode = nextExpression.evaluate(context);
|
||||||
|
compiledCode += expressionCode;
|
||||||
|
index = nextExpression.position.end;
|
||||||
|
} else {
|
||||||
|
compiledCode += code.substring(index, code.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return intermediateCode;
|
return compiledCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureValuesProvided(names: string[], nameValues: ParameterValueDictionary) {
|
function extractRequiredParameterNames(
|
||||||
nameValues = nameValues || {};
|
expressions: readonly IExpression[]): string[] {
|
||||||
const notProvidedNames = names.filter((name) => !Boolean(nameValues[name]));
|
const usedParameterNames = expressions
|
||||||
if (notProvidedNames.length) {
|
.map((e) => e.parameters.all
|
||||||
throw new Error(`parameter value(s) not provided for: ${printList(notProvidedNames)}`);
|
.filter((p) => !p.isOptional)
|
||||||
|
.map((p) => p.name))
|
||||||
|
.filter((p) => p)
|
||||||
|
.flat();
|
||||||
|
const uniqueParameterNames = Array.from(new Set(usedParameterNames));
|
||||||
|
return uniqueParameterNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureParamsUsedInCodeHasArgsProvided(
|
||||||
|
expressions: readonly IExpression[],
|
||||||
|
providedArgs: IReadOnlyFunctionCallArgumentCollection): void {
|
||||||
|
const usedParameterNames = extractRequiredParameterNames(expressions);
|
||||||
|
if (!usedParameterNames?.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const notProvidedParameters = usedParameterNames
|
||||||
|
.filter((parameterName) => !providedArgs.hasArgument(parameterName));
|
||||||
|
if (notProvidedParameters.length) {
|
||||||
|
throw new Error(`parameter value(s) not provided for: ${printList(notProvidedParameters)} but used in code`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
export interface ParameterValueDictionary { [parameterName: string]: string; }
|
import { IReadOnlyFunctionCallArgumentCollection } from '../Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
export interface IExpressionsCompiler {
|
export interface IExpressionsCompiler {
|
||||||
compileExpressions(code: string, parameters?: ParameterValueDictionary): string;
|
compileExpressions(
|
||||||
|
code: string,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection): string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
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,27 @@
|
|||||||
|
import { IExpression } from '../Expression/IExpression';
|
||||||
|
import { IExpressionParser } from './IExpressionParser';
|
||||||
|
import { ParameterSubstitutionParser } from '../SyntaxParsers/ParameterSubstitutionParser';
|
||||||
|
import { WithParser } from '../SyntaxParsers/WithParser';
|
||||||
|
|
||||||
|
const Parsers = [
|
||||||
|
new ParameterSubstitutionParser(),
|
||||||
|
new WithParser(),
|
||||||
|
];
|
||||||
|
|
||||||
|
export class CompositeExpressionParser implements IExpressionParser {
|
||||||
|
public constructor(private readonly leafs: readonly IExpressionParser[] = Parsers) {
|
||||||
|
if (leafs.some((leaf) => !leaf)) {
|
||||||
|
throw new Error('undefined leaf');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public findExpressions(code: string): IExpression[] {
|
||||||
|
const expressions = new Array<IExpression>();
|
||||||
|
for (const parser of this.leafs) {
|
||||||
|
const newExpressions = parser.findExpressions(code);
|
||||||
|
if (newExpressions && newExpressions.length) {
|
||||||
|
expressions.push(...newExpressions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expressions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { IExpression } from '../Expression/IExpression';
|
||||||
|
|
||||||
|
export interface IExpressionParser {
|
||||||
|
findExpressions(code: string): IExpression[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
export class ExpressionRegexBuilder {
|
||||||
|
private readonly parts = new Array<string>();
|
||||||
|
|
||||||
|
public expectCharacters(characters: string) {
|
||||||
|
return this.addRawRegex(
|
||||||
|
characters
|
||||||
|
.replaceAll('$', '\\$')
|
||||||
|
.replaceAll('.', '\\.'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public expectOneOrMoreWhitespaces() {
|
||||||
|
return this
|
||||||
|
.addRawRegex('\\s+');
|
||||||
|
}
|
||||||
|
|
||||||
|
public matchPipeline() {
|
||||||
|
return this
|
||||||
|
.expectZeroOrMoreWhitespaces()
|
||||||
|
.addRawRegex('(\\|\\s*.+?)?');
|
||||||
|
}
|
||||||
|
|
||||||
|
public matchUntilFirstWhitespace() {
|
||||||
|
return this
|
||||||
|
.addRawRegex('([^|\\s]+)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public matchAnythingExceptSurroundingWhitespaces() {
|
||||||
|
return this
|
||||||
|
.expectZeroOrMoreWhitespaces()
|
||||||
|
.addRawRegex('(.+?)')
|
||||||
|
.expectZeroOrMoreWhitespaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
public expectExpressionStart() {
|
||||||
|
return this
|
||||||
|
.expectCharacters('{{')
|
||||||
|
.expectZeroOrMoreWhitespaces();
|
||||||
|
}
|
||||||
|
|
||||||
|
public expectExpressionEnd() {
|
||||||
|
return this
|
||||||
|
.expectZeroOrMoreWhitespaces()
|
||||||
|
.expectCharacters('}}');
|
||||||
|
}
|
||||||
|
|
||||||
|
public buildRegExp(): RegExp {
|
||||||
|
return new RegExp(this.parts.join(''), 'g');
|
||||||
|
}
|
||||||
|
|
||||||
|
private expectZeroOrMoreWhitespaces() {
|
||||||
|
return this
|
||||||
|
.addRawRegex('\\s*');
|
||||||
|
}
|
||||||
|
private addRawRegex(regex: string) {
|
||||||
|
this.parts.push(regex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { IExpressionParser } from '../IExpressionParser';
|
||||||
|
import { ExpressionPosition } from '../../Expression/ExpressionPosition';
|
||||||
|
import { IExpression } from '../../Expression/IExpression';
|
||||||
|
import { Expression, ExpressionEvaluator } from '../../Expression/Expression';
|
||||||
|
import { IFunctionParameter } from '../../../Function/Parameter/IFunctionParameter';
|
||||||
|
import { FunctionParameterCollection } from '../../../Function/Parameter/FunctionParameterCollection';
|
||||||
|
|
||||||
|
export abstract class RegexParser implements IExpressionParser {
|
||||||
|
protected abstract readonly regex: RegExp;
|
||||||
|
|
||||||
|
public findExpressions(code: string): IExpression[] {
|
||||||
|
return Array.from(this.findRegexExpressions(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract buildExpression(match: RegExpMatchArray): IPrimitiveExpression;
|
||||||
|
|
||||||
|
private* findRegexExpressions(code: string): Iterable<IExpression> {
|
||||||
|
const matches = Array.from(code.matchAll(this.regex));
|
||||||
|
for (const match of matches) {
|
||||||
|
const startPos = match.index;
|
||||||
|
const endPos = startPos + match[0].length;
|
||||||
|
let position: ExpressionPosition;
|
||||||
|
try {
|
||||||
|
position = new ExpressionPosition(startPos, endPos);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`[${this.constructor.name}] invalid script position: ${error.message}\nRegex ${this.regex}\nCode: ${code}`);
|
||||||
|
}
|
||||||
|
const primitiveExpression = this.buildExpression(match);
|
||||||
|
const parameters = getParameters(primitiveExpression);
|
||||||
|
const expression = new Expression(position, primitiveExpression.evaluator, parameters);
|
||||||
|
yield expression;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPrimitiveExpression {
|
||||||
|
evaluator: ExpressionEvaluator;
|
||||||
|
parameters?: readonly IFunctionParameter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getParameters(
|
||||||
|
expression: IPrimitiveExpression): FunctionParameterCollection {
|
||||||
|
const parameters = new FunctionParameterCollection();
|
||||||
|
for (const parameter of expression.parameters || []) {
|
||||||
|
parameters.addParameter(parameter);
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface IPipe {
|
||||||
|
readonly name: string;
|
||||||
|
apply(input: string): string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface IPipelineCompiler {
|
||||||
|
compile(value: string, pipeline: string): string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { IPipe } from '../IPipe';
|
||||||
|
|
||||||
|
export class EscapeDoubleQuotes implements IPipe {
|
||||||
|
public readonly name: string = 'escapeDoubleQuotes';
|
||||||
|
public apply(raw: string): string {
|
||||||
|
return raw?.replaceAll('"', '"^""');
|
||||||
|
/*
|
||||||
|
"^"" is the most robust and stable choice.
|
||||||
|
Other options:
|
||||||
|
""
|
||||||
|
Breaks, because it is fundamentally unsupported
|
||||||
|
""""
|
||||||
|
Does not work with consecutive double quotes.
|
||||||
|
E.g. PowerShell -Command "$name='aq'; Write-Host """"Disabled `""""$name`"""""""";"
|
||||||
|
Works when using: PowerShell -Command "$name='aq'; Write-Host "^""Disabled `"^""$name`"^"" "^"";"
|
||||||
|
\"
|
||||||
|
May break as they are interpreted by cmd.exe as metacharacters breaking the command
|
||||||
|
E.g. PowerShell -Command "Write-Host 'Hello \"w&orld\"'" does not work due to unescaped "&"
|
||||||
|
Works when using: PowerShell -Command "Write-Host 'Hello "^""w&orld"^""'"
|
||||||
|
\""
|
||||||
|
Normalizes interior whitespace
|
||||||
|
E.g. PowerShell -Command "\""a& c\"".length", outputs 4 and discards one of two whitespaces
|
||||||
|
Works when using "^"": PowerShell -Command ""^""a& c"^"".length"
|
||||||
|
A good explanation: https://stackoverflow.com/a/31413730
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import { IPipe } from '../IPipe';
|
||||||
|
|
||||||
|
export class InlinePowerShell implements IPipe {
|
||||||
|
public readonly name: string = 'inlinePowerShell';
|
||||||
|
public apply(code: string): string {
|
||||||
|
if (!code || !hasLines(code)) {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
code = inlineComments(code);
|
||||||
|
code = mergeLinesWithBacktick(code);
|
||||||
|
code = mergeHereStrings(code);
|
||||||
|
const lines = getLines(code)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter((line) => line.length > 0);
|
||||||
|
return lines
|
||||||
|
.join('; ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasLines(text: string) {
|
||||||
|
return text.includes('\n') || text.includes('\r');
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Line comments using "#" are replaced with inline comment syntax <# comment.. #>
|
||||||
|
Otherwise single # comments out rest of the code
|
||||||
|
*/
|
||||||
|
function inlineComments(code: string): string {
|
||||||
|
const makeInlineComment = (comment: string) => {
|
||||||
|
const value = comment?.trim();
|
||||||
|
if (!value) {
|
||||||
|
return '<##>';
|
||||||
|
}
|
||||||
|
return `<# ${value} #>`;
|
||||||
|
};
|
||||||
|
return code.replaceAll(/<#.*?#>|#(.*)/g, (match, captureComment) => {
|
||||||
|
if (captureComment === undefined) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
return makeInlineComment(captureComment);
|
||||||
|
});
|
||||||
|
/*
|
||||||
|
Other alternatives considered:
|
||||||
|
--------------------------
|
||||||
|
/#(?<!<#)(?![<>])(.*)$/gm
|
||||||
|
-------------------------
|
||||||
|
✅ Simple, yet matches and captures only what's necessary
|
||||||
|
❌ Fails to match some cases
|
||||||
|
❌ `Write-Host "hi" # Comment ending line inline comment but not one #>`
|
||||||
|
❌ `Write-Host "hi" <#Comment starting like inline comment start but not one`
|
||||||
|
❌ `Write-Host "hi" #>Comment starting like inline comment end but not one`
|
||||||
|
❌ Uses lookbehind
|
||||||
|
Safari does not yet support lookbehind and syntax, leading application to not
|
||||||
|
load and throw "Invalid regular expression: invalid group specifier name"
|
||||||
|
https://caniuse.com/js-regexp-lookbehind
|
||||||
|
⏩ Usage
|
||||||
|
return code.replaceAll(/#(?<!<#)(?![<>])(.*)$/gm, (match, captureComment) => {
|
||||||
|
return makeInlineComment(captureComment)
|
||||||
|
});
|
||||||
|
----------------
|
||||||
|
/<#.*?#>|#(.*)/g
|
||||||
|
----------------
|
||||||
|
✅ Simple yet affective
|
||||||
|
❌ Matches all comments, but only captures dash comments
|
||||||
|
❌ Fails to match some cases
|
||||||
|
❌ `Write-Host "hi" # Comment ending line inline comment but not one #>`
|
||||||
|
❌ `Write-Host "hi" <#Comment starting like inline comment start but not one`
|
||||||
|
⏩ Usage
|
||||||
|
return code.replaceAll(/<#.*?#>|#(.*)/g, (match, captureComment) => {
|
||||||
|
if (captureComment === undefined) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
return makeInlineComment(captureComment);
|
||||||
|
});
|
||||||
|
------------------------------------
|
||||||
|
/(^(?:<#.*?#>|[^#])*)(?:(#)(.*))?/gm
|
||||||
|
------------------------------------
|
||||||
|
✅ Covers all cases
|
||||||
|
❌ Matches every line, three capture groups are used to build result
|
||||||
|
⏩ Usage
|
||||||
|
return code.replaceAll(/(^(?:<#.*?#>|[^#])*)(?:(#)(.*))?/gm,
|
||||||
|
(match, captureLeft, captureDash, captureComment) => {
|
||||||
|
if (!captureDash) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
return captureLeft + makeInlineComment(captureComment);
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLines(code: string): string [] {
|
||||||
|
return (code?.split(/\r\n|\r|\n/) || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Merges inline here-strings to a single lined string with Windows line terminator (\r\n)
|
||||||
|
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules#here-strings
|
||||||
|
*/
|
||||||
|
function mergeHereStrings(code: string) {
|
||||||
|
const regex = /@(['"])\s*(?:\r\n|\r|\n)((.|\n|\r)+?)(\r\n|\r|\n)\1@/g;
|
||||||
|
return code.replaceAll(regex, (_$, quotes, scope) => {
|
||||||
|
const newString = getHereStringHandler(quotes);
|
||||||
|
const escaped = scope.replaceAll(quotes, newString.escapedQuotes);
|
||||||
|
const lines = getLines(escaped);
|
||||||
|
const inlined = lines.join(newString.separator);
|
||||||
|
const quoted = `${newString.quotesAround}${inlined}${newString.quotesAround}`;
|
||||||
|
return quoted;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
interface IInlinedHereString {
|
||||||
|
readonly quotesAround: string;
|
||||||
|
readonly escapedQuotes: string;
|
||||||
|
readonly separator: string;
|
||||||
|
}
|
||||||
|
// We handle @' and @" differently so single quotes are interpreted literally and doubles are expandable
|
||||||
|
function getHereStringHandler(quotes: string): IInlinedHereString {
|
||||||
|
const expandableNewLine = '`r`n';
|
||||||
|
switch (quotes) {
|
||||||
|
case '\'':
|
||||||
|
return {
|
||||||
|
quotesAround: '\'',
|
||||||
|
escapedQuotes: '\'\'',
|
||||||
|
separator: `\'+"${expandableNewLine}"+\'`,
|
||||||
|
};
|
||||||
|
case '"':
|
||||||
|
return {
|
||||||
|
quotesAround: '"',
|
||||||
|
escapedQuotes: '`"',
|
||||||
|
separator: expandableNewLine,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(`expected quotes: ${quotes}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Input ->
|
||||||
|
Get-Service * `
|
||||||
|
Sort-Object StartType `
|
||||||
|
Format-Table Name, ServiceType, Status -AutoSize
|
||||||
|
Output ->
|
||||||
|
Get-Service * | Sort-Object StartType | Format-Table -AutoSize
|
||||||
|
*/
|
||||||
|
function mergeLinesWithBacktick(code: string) {
|
||||||
|
/*
|
||||||
|
The regex actually wraps any whitespace character after backtick and before newline
|
||||||
|
However, this is not always the case for PowerShell.
|
||||||
|
I see two behaviors:
|
||||||
|
1. If inside string, it's accepted (inside " or ')
|
||||||
|
2. If part of a command, PowerShell throws "An empty pipe element is not allowed"
|
||||||
|
However we don't need to be so robust and handle this complexity (yet), so for easier regex
|
||||||
|
we wrap it anyway
|
||||||
|
*/
|
||||||
|
return code.replaceAll(/ +`\s*(?:\r\n|\r|\n)\s*/g, ' ');
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { IPipe } from './IPipe';
|
||||||
|
import { InlinePowerShell } from './PipeDefinitions/InlinePowerShell';
|
||||||
|
import { EscapeDoubleQuotes } from './PipeDefinitions/EscapeDoubleQuotes';
|
||||||
|
|
||||||
|
const RegisteredPipes = [
|
||||||
|
new EscapeDoubleQuotes(),
|
||||||
|
new InlinePowerShell(),
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface IPipeFactory {
|
||||||
|
get(pipeName: string): IPipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PipeFactory implements IPipeFactory {
|
||||||
|
private readonly pipes = new Map<string, IPipe>();
|
||||||
|
constructor(pipes: readonly IPipe[] = RegisteredPipes) {
|
||||||
|
if (pipes.some((pipe) => !pipe)) {
|
||||||
|
throw new Error('undefined pipe in list');
|
||||||
|
}
|
||||||
|
for (const pipe of pipes) {
|
||||||
|
this.registerPipe(pipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public get(pipeName: string): IPipe {
|
||||||
|
validatePipeName(pipeName);
|
||||||
|
if (!this.pipes.has(pipeName)) {
|
||||||
|
throw new Error(`Unknown pipe: "${pipeName}"`);
|
||||||
|
}
|
||||||
|
return this.pipes.get(pipeName);
|
||||||
|
}
|
||||||
|
private registerPipe(pipe: IPipe): void {
|
||||||
|
validatePipeName(pipe.name);
|
||||||
|
if (this.pipes.has(pipe.name)) {
|
||||||
|
throw new Error(`Pipe name must be unique: "${pipe.name}"`);
|
||||||
|
}
|
||||||
|
this.pipes.set(pipe.name, pipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function validatePipeName(name: string) {
|
||||||
|
if (!name) {
|
||||||
|
throw new Error('empty pipe name');
|
||||||
|
}
|
||||||
|
if (!/^[a-z][A-Za-z]*$/.test(name)) {
|
||||||
|
throw new Error(`Pipe name should be camelCase: "${name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { IPipeFactory, PipeFactory } from './PipeFactory';
|
||||||
|
import { IPipelineCompiler } from './IPipelineCompiler';
|
||||||
|
|
||||||
|
export class PipelineCompiler implements IPipelineCompiler {
|
||||||
|
constructor(private readonly factory: IPipeFactory = new PipeFactory()) { }
|
||||||
|
public compile(value: string, pipeline: string): string {
|
||||||
|
ensureValidArguments(value, pipeline);
|
||||||
|
const pipeNames = extractPipeNames(pipeline);
|
||||||
|
const pipes = pipeNames.map((pipeName) => this.factory.get(pipeName));
|
||||||
|
for (const pipe of pipes) {
|
||||||
|
value = pipe.apply(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPipeNames(pipeline: string): string[] {
|
||||||
|
return pipeline
|
||||||
|
.trim()
|
||||||
|
.split('|')
|
||||||
|
.slice(1)
|
||||||
|
.map((p) => p.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureValidArguments(value: string, pipeline: string) {
|
||||||
|
if (!value) { throw new Error('undefined value'); }
|
||||||
|
if (!pipeline) { throw new Error('undefined pipeline'); }
|
||||||
|
if (!pipeline.trimStart().startsWith('|')) {
|
||||||
|
throw new Error('pipeline does not start with pipe');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { RegexParser, IPrimitiveExpression } from '../Parser/Regex/RegexParser';
|
||||||
|
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||||
|
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
|
||||||
|
|
||||||
|
export class ParameterSubstitutionParser extends RegexParser {
|
||||||
|
protected readonly regex = new ExpressionRegexBuilder()
|
||||||
|
.expectExpressionStart()
|
||||||
|
.expectCharacters('$')
|
||||||
|
.matchUntilFirstWhitespace() // First match: Parameter name
|
||||||
|
.matchPipeline() // Second match: Pipeline
|
||||||
|
.expectExpressionEnd()
|
||||||
|
.buildRegExp();
|
||||||
|
|
||||||
|
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||||
|
const parameterName = match[1];
|
||||||
|
const pipeline = match[2];
|
||||||
|
return {
|
||||||
|
parameters: [ new FunctionParameter(parameterName, false) ],
|
||||||
|
evaluator: (context) => {
|
||||||
|
const argumentValue = context.args.getArgument(parameterName).argumentValue;
|
||||||
|
if (!pipeline) {
|
||||||
|
return argumentValue;
|
||||||
|
}
|
||||||
|
return context.pipelineCompiler.compile(argumentValue, pipeline);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { RegexParser, IPrimitiveExpression } from '../Parser/Regex/RegexParser';
|
||||||
|
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||||
|
import { ExpressionRegexBuilder } from '../Parser/Regex/ExpressionRegexBuilder';
|
||||||
|
|
||||||
|
export class WithParser extends RegexParser {
|
||||||
|
protected readonly regex = new ExpressionRegexBuilder()
|
||||||
|
// {{ with $parameterName }}
|
||||||
|
.expectExpressionStart()
|
||||||
|
.expectCharacters('with')
|
||||||
|
.expectOneOrMoreWhitespaces()
|
||||||
|
.expectCharacters('$')
|
||||||
|
.matchUntilFirstWhitespace() // First match: parameter name
|
||||||
|
.expectExpressionEnd()
|
||||||
|
// ...
|
||||||
|
.matchAnythingExceptSurroundingWhitespaces() // Second match: Scope text
|
||||||
|
// {{ end }}
|
||||||
|
.expectExpressionStart()
|
||||||
|
.expectCharacters('end')
|
||||||
|
.expectExpressionEnd()
|
||||||
|
.buildRegExp();
|
||||||
|
|
||||||
|
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||||
|
const parameterName = match[1];
|
||||||
|
const scopeText = match[2];
|
||||||
|
return {
|
||||||
|
parameters: [ new FunctionParameter(parameterName, true) ],
|
||||||
|
evaluator: (context) => {
|
||||||
|
const argumentValue = context.args.hasArgument(parameterName) ?
|
||||||
|
context.args.getArgument(parameterName).argumentValue
|
||||||
|
: undefined;
|
||||||
|
if (!argumentValue) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return replaceEachScopeSubstitution(scopeText, (pipeline) => {
|
||||||
|
if (!pipeline) {
|
||||||
|
return argumentValue;
|
||||||
|
}
|
||||||
|
return context.pipelineCompiler.compile(argumentValue, pipeline);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScopeSubstitutionRegEx = new ExpressionRegexBuilder()
|
||||||
|
// {{ . | pipeName }}
|
||||||
|
.expectExpressionStart()
|
||||||
|
.expectCharacters('.')
|
||||||
|
.matchPipeline() // First match: pipeline
|
||||||
|
.expectExpressionEnd()
|
||||||
|
.buildRegExp();
|
||||||
|
|
||||||
|
function replaceEachScopeSubstitution(scopeText: string, replacer: (pipeline: string) => string) {
|
||||||
|
// Not using /{{\s*.\s*(?:(\|\s*[^{}]*?)\s*)?}}/g for not matching brackets, but let pipeline compiler fail on those
|
||||||
|
return scopeText.replaceAll(ScopeSubstitutionRegEx, (_$, match1 ) => {
|
||||||
|
return replacer(match1);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { IFunctionCallArgument } from './IFunctionCallArgument';
|
||||||
|
import { ensureValidParameterName } from '../../Shared/ParameterNameValidator';
|
||||||
|
|
||||||
|
export class FunctionCallArgument implements IFunctionCallArgument {
|
||||||
|
constructor(
|
||||||
|
public readonly parameterName: string,
|
||||||
|
public readonly argumentValue: string) {
|
||||||
|
ensureValidParameterName(parameterName);
|
||||||
|
if (!argumentValue) {
|
||||||
|
throw new Error(`undefined argument value for "${parameterName}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { IFunctionCallArgument } from './IFunctionCallArgument';
|
||||||
|
import { IFunctionCallArgumentCollection } from './IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
|
export class FunctionCallArgumentCollection implements IFunctionCallArgumentCollection {
|
||||||
|
private readonly arguments = new Map<string, IFunctionCallArgument>();
|
||||||
|
public addArgument(argument: IFunctionCallArgument): void {
|
||||||
|
if (!argument) {
|
||||||
|
throw new Error('undefined argument');
|
||||||
|
}
|
||||||
|
if (this.hasArgument(argument.parameterName)) {
|
||||||
|
throw new Error(`argument value for parameter ${argument.parameterName} is already provided`);
|
||||||
|
}
|
||||||
|
this.arguments.set(argument.parameterName, argument);
|
||||||
|
}
|
||||||
|
public getAllParameterNames(): string[] {
|
||||||
|
return Array.from(this.arguments.keys());
|
||||||
|
}
|
||||||
|
public hasArgument(parameterName: string): boolean {
|
||||||
|
if (!parameterName) {
|
||||||
|
throw new Error('undefined parameter name');
|
||||||
|
}
|
||||||
|
return this.arguments.has(parameterName);
|
||||||
|
}
|
||||||
|
public getArgument(parameterName: string): IFunctionCallArgument {
|
||||||
|
if (!parameterName) {
|
||||||
|
throw new Error('undefined parameter name');
|
||||||
|
}
|
||||||
|
const arg = this.arguments.get(parameterName);
|
||||||
|
if (!arg) {
|
||||||
|
throw new Error(`parameter does not exist: ${parameterName}`);
|
||||||
|
}
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface IFunctionCallArgument {
|
||||||
|
readonly parameterName: string;
|
||||||
|
readonly argumentValue: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { IFunctionCallArgument } from './IFunctionCallArgument';
|
||||||
|
|
||||||
|
export interface IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
getArgument(parameterName: string): IFunctionCallArgument;
|
||||||
|
getAllParameterNames(): string[];
|
||||||
|
hasArgument(parameterName: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFunctionCallArgumentCollection extends IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
addArgument(argument: IFunctionCallArgument): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
|
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
||||||
|
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
||||||
|
import { IExpressionsCompiler } from '../../../Expressions/IExpressionsCompiler';
|
||||||
|
import { ExpressionsCompiler } from '../../../Expressions/ExpressionsCompiler';
|
||||||
|
import { ISharedFunction, IFunctionCode } from '../../ISharedFunction';
|
||||||
|
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||||
|
import { FunctionCall } from '../FunctionCall';
|
||||||
|
import { FunctionCallArgumentCollection } from '../Argument/FunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||||
|
|
||||||
|
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
||||||
|
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
||||||
|
|
||||||
|
protected constructor(
|
||||||
|
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public compileCall(
|
||||||
|
calls: IFunctionCall[],
|
||||||
|
functions: ISharedFunctionCollection): ICompiledCode {
|
||||||
|
if (!functions) { throw new Error('undefined functions'); }
|
||||||
|
if (!calls) { throw new Error('undefined calls'); }
|
||||||
|
if (calls.some((f) => !f)) { throw new Error('undefined function call'); }
|
||||||
|
const context: ICompilationContext = {
|
||||||
|
allFunctions: functions,
|
||||||
|
callSequence: calls,
|
||||||
|
expressionsCompiler: this.expressionsCompiler,
|
||||||
|
};
|
||||||
|
const code = compileCallSequence(context);
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICompilationContext {
|
||||||
|
allFunctions: ISharedFunctionCollection;
|
||||||
|
callSequence: readonly IFunctionCall[];
|
||||||
|
expressionsCompiler: IExpressionsCompiler;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ICompiledFunctionCall {
|
||||||
|
readonly code: string;
|
||||||
|
readonly revertCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileCallSequence(context: ICompilationContext): ICompiledFunctionCall {
|
||||||
|
const compiledFunctions = new Array<ICompiledFunctionCall>();
|
||||||
|
for (const call of context.callSequence) {
|
||||||
|
const compiledCode = compileSingleCall(call, context);
|
||||||
|
compiledFunctions.push(...compiledCode);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: merge(compiledFunctions.map((f) => f.code)),
|
||||||
|
revertCode: merge(compiledFunctions.map((f) => f.revertCode)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileSingleCall(call: IFunctionCall, context: ICompilationContext): ICompiledFunctionCall[] {
|
||||||
|
const func = context.allFunctions.getFunctionByName(call.functionName);
|
||||||
|
ensureThatCallArgumentsExistInParameterDefinition(func, call.args);
|
||||||
|
if (func.body.code) { // Function with inline code
|
||||||
|
const compiledCode = compileCode(func.body.code, call.args, context.expressionsCompiler);
|
||||||
|
return [ compiledCode ];
|
||||||
|
} else { // Function with inner calls
|
||||||
|
return func.body.calls
|
||||||
|
.map((innerCall) => {
|
||||||
|
const compiledArgs = compileArgs(innerCall.args, call.args, context.expressionsCompiler);
|
||||||
|
const compiledCall = new FunctionCall(innerCall.functionName, compiledArgs);
|
||||||
|
return compileSingleCall(compiledCall, context);
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileCode(
|
||||||
|
code: IFunctionCode,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
compiler: IExpressionsCompiler): ICompiledFunctionCall {
|
||||||
|
return {
|
||||||
|
code: compiler.compileExpressions(code.do, args),
|
||||||
|
revertCode: compiler.compileExpressions(code.revert, args),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileArgs(
|
||||||
|
argsToCompile: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection,
|
||||||
|
compiler: IExpressionsCompiler,
|
||||||
|
): IReadOnlyFunctionCallArgumentCollection {
|
||||||
|
const compiledArgs = new FunctionCallArgumentCollection();
|
||||||
|
for (const parameterName of argsToCompile.getAllParameterNames()) {
|
||||||
|
const argumentValue = argsToCompile.getArgument(parameterName).argumentValue;
|
||||||
|
const compiledValue = compiler.compileExpressions(argumentValue, args);
|
||||||
|
const newArgument = new FunctionCallArgument(parameterName, compiledValue);
|
||||||
|
compiledArgs.addArgument(newArgument);
|
||||||
|
}
|
||||||
|
return compiledArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function merge(codeParts: readonly string[]): string {
|
||||||
|
return codeParts
|
||||||
|
.filter((part) => part?.length > 0)
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureThatCallArgumentsExistInParameterDefinition(
|
||||||
|
func: ISharedFunction,
|
||||||
|
args: IReadOnlyFunctionCallArgumentCollection): void {
|
||||||
|
const callArgumentNames = args.getAllParameterNames();
|
||||||
|
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
|
||||||
|
const unexpectedParameters = findUnexpectedParameters(callArgumentNames, functionParameterNames);
|
||||||
|
throwIfNotEmpty(func.name, unexpectedParameters, functionParameterNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findUnexpectedParameters(
|
||||||
|
callArgumentNames: string[],
|
||||||
|
functionParameterNames: string[]): string[] {
|
||||||
|
if (!callArgumentNames.length && !functionParameterNames.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return callArgumentNames
|
||||||
|
.filter((callParam) => !functionParameterNames.includes(callParam));
|
||||||
|
}
|
||||||
|
|
||||||
|
function throwIfNotEmpty(
|
||||||
|
functionName: string,
|
||||||
|
unexpectedParameters: string[],
|
||||||
|
expectedParameters: string[]) {
|
||||||
|
if (!unexpectedParameters.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
`Function "${functionName}" has unexpected parameter(s) provided: ` +
|
||||||
|
`"${unexpectedParameters.join('", "')}"` +
|
||||||
|
'. Expected parameter(s): ' +
|
||||||
|
(expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { ICompiledCode } from './ICompiledCode';
|
||||||
|
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||||
|
import { IFunctionCall } from '../IFunctionCall';
|
||||||
|
|
||||||
|
export interface IFunctionCallCompiler {
|
||||||
|
compileCall(
|
||||||
|
calls: IFunctionCall[],
|
||||||
|
functions: ISharedFunctionCollection): ICompiledCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
|
import { IFunctionCall } from './IFunctionCall';
|
||||||
|
|
||||||
|
export class FunctionCall implements IFunctionCall {
|
||||||
|
constructor(
|
||||||
|
public readonly functionName: string,
|
||||||
|
public readonly args: IReadOnlyFunctionCallArgumentCollection) {
|
||||||
|
if (!functionName) {
|
||||||
|
throw new Error('empty function name in function call');
|
||||||
|
}
|
||||||
|
if (!args) {
|
||||||
|
throw new Error('undefined args');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { FunctionCallData, FunctionCallsData } from 'js-yaml-loader!@/*';
|
||||||
|
import { IFunctionCall } from './IFunctionCall';
|
||||||
|
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
||||||
|
import { FunctionCall } from './FunctionCall';
|
||||||
|
|
||||||
|
export function parseFunctionCalls(calls: FunctionCallsData): IFunctionCall[] {
|
||||||
|
if (!calls) {
|
||||||
|
throw new Error('undefined call data');
|
||||||
|
}
|
||||||
|
const sequence = getCallSequence(calls);
|
||||||
|
return sequence.map((call) => parseFunctionCall(call));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
|
||||||
|
if (typeof calls !== 'object') {
|
||||||
|
throw new Error('called function(s) must be an object');
|
||||||
|
}
|
||||||
|
if (calls instanceof Array) {
|
||||||
|
return calls as FunctionCallData[];
|
||||||
|
}
|
||||||
|
return [ calls as FunctionCallData ];
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
||||||
|
if (!call) {
|
||||||
|
throw new Error(`undefined function call`);
|
||||||
|
}
|
||||||
|
const args = new FunctionCallArgumentCollection();
|
||||||
|
for (const parameterName of Object.keys(call.parameters || {})) {
|
||||||
|
const arg = new FunctionCallArgument(parameterName, call.parameters[parameterName]);
|
||||||
|
args.addArgument(arg);
|
||||||
|
}
|
||||||
|
return new FunctionCall(call.function, args);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||||
|
|
||||||
|
export interface IFunctionCall {
|
||||||
|
readonly functionName: string;
|
||||||
|
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { FunctionData } from 'js-yaml-loader!*';
|
|
||||||
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
|
||||||
|
|
||||||
export interface IFunctionCompiler {
|
|
||||||
compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,24 @@
|
|||||||
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
import { IFunctionCall } from '../Function/Call/IFunctionCall';
|
||||||
|
|
||||||
export interface ISharedFunction {
|
export interface ISharedFunction {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
readonly parameters?: readonly string[];
|
readonly parameters: IReadOnlyFunctionParameterCollection;
|
||||||
readonly code: string;
|
readonly body: ISharedFunctionBody;
|
||||||
readonly revertCode?: string;
|
}
|
||||||
|
|
||||||
|
export interface ISharedFunctionBody {
|
||||||
|
readonly type: FunctionBodyType;
|
||||||
|
readonly code: IFunctionCode;
|
||||||
|
readonly calls: readonly IFunctionCall[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FunctionBodyType {
|
||||||
|
Code,
|
||||||
|
Calls,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFunctionCode {
|
||||||
|
readonly do: string;
|
||||||
|
readonly revert?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { FunctionData } from 'js-yaml-loader!@/*';
|
||||||
|
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
|
|
||||||
|
export interface ISharedFunctionsParser {
|
||||||
|
parseFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { IFunctionParameter } from './IFunctionParameter';
|
||||||
|
import { ensureValidParameterName } from '../Shared/ParameterNameValidator';
|
||||||
|
|
||||||
|
export class FunctionParameter implements IFunctionParameter {
|
||||||
|
constructor(
|
||||||
|
public readonly name: string,
|
||||||
|
public readonly isOptional: boolean) {
|
||||||
|
ensureValidParameterName(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { IFunctionParameterCollection } from './IFunctionParameterCollection';
|
||||||
|
import { IFunctionParameter } from './IFunctionParameter';
|
||||||
|
|
||||||
|
export class FunctionParameterCollection implements IFunctionParameterCollection {
|
||||||
|
private parameters = new Array<IFunctionParameter>();
|
||||||
|
|
||||||
|
public get all(): readonly IFunctionParameter[] {
|
||||||
|
return this.parameters;
|
||||||
|
}
|
||||||
|
public addParameter(parameter: IFunctionParameter) {
|
||||||
|
this.ensureValidParameter(parameter);
|
||||||
|
this.parameters.push(parameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private includesName(name: string) {
|
||||||
|
return this.parameters.find((existingParameter) => existingParameter.name === name);
|
||||||
|
}
|
||||||
|
private ensureValidParameter(parameter: IFunctionParameter) {
|
||||||
|
if (!parameter) {
|
||||||
|
throw new Error('undefined parameter');
|
||||||
|
}
|
||||||
|
if (this.includesName(parameter.name)) {
|
||||||
|
throw new Error(`duplicate parameter name: "${parameter.name}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface IFunctionParameter {
|
||||||
|
readonly name: string;
|
||||||
|
readonly isOptional: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { IFunctionParameter } from './IFunctionParameter';
|
||||||
|
|
||||||
|
export interface IReadOnlyFunctionParameterCollection {
|
||||||
|
readonly all: readonly IFunctionParameter[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFunctionParameterCollection extends IReadOnlyFunctionParameterCollection {
|
||||||
|
addParameter(parameter: IFunctionParameter): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export function ensureValidParameterName(parameterName: string) {
|
||||||
|
if (!parameterName) {
|
||||||
|
throw new Error('undefined parameter name');
|
||||||
|
}
|
||||||
|
if (!parameterName.match(/^[0-9a-zA-Z]+$/)) {
|
||||||
|
throw new Error(`parameter name must be alphanumeric but it was "${parameterName}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,49 @@
|
|||||||
import { ISharedFunction } from './ISharedFunction';
|
import { IFunctionCall } from '../Function/Call/IFunctionCall';
|
||||||
|
import { FunctionBodyType, IFunctionCode, ISharedFunction, ISharedFunctionBody } from './ISharedFunction';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
|
||||||
export class SharedFunction implements ISharedFunction {
|
export function createCallerFunction(
|
||||||
|
name: string,
|
||||||
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
|
callSequence: readonly IFunctionCall[]): ISharedFunction {
|
||||||
|
if (!callSequence) {
|
||||||
|
throw new Error(`undefined call sequence in function "${name}"`);
|
||||||
|
}
|
||||||
|
if (!callSequence.length) {
|
||||||
|
throw new Error(`empty call sequence in function "${name}"`);
|
||||||
|
}
|
||||||
|
return new SharedFunction(name, parameters, callSequence, FunctionBodyType.Calls);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createFunctionWithInlineCode(
|
||||||
|
name: string,
|
||||||
|
parameters: IReadOnlyFunctionParameterCollection,
|
||||||
|
code: string,
|
||||||
|
revertCode?: string): ISharedFunction {
|
||||||
|
if (!code) {
|
||||||
|
throw new Error(`undefined code in function "${name}"`);
|
||||||
|
}
|
||||||
|
const content: IFunctionCode = {
|
||||||
|
do: code,
|
||||||
|
revert: revertCode,
|
||||||
|
};
|
||||||
|
return new SharedFunction(name, parameters, content, FunctionBodyType.Code);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SharedFunction implements ISharedFunction {
|
||||||
|
public readonly body: ISharedFunctionBody;
|
||||||
constructor(
|
constructor(
|
||||||
public readonly name: string,
|
public readonly name: string,
|
||||||
public readonly parameters: readonly string[],
|
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
||||||
public readonly code: string,
|
content: IFunctionCode | readonly IFunctionCall[],
|
||||||
public readonly revertCode: string,
|
bodyType: FunctionBodyType,
|
||||||
) {
|
) {
|
||||||
if (!name) { throw new Error('undefined function name'); }
|
if (!name) { throw new Error('undefined function name'); }
|
||||||
if (!code) { throw new Error(`undefined function ("${name}") code`); }
|
if (!parameters) { throw new Error(`undefined parameters`); }
|
||||||
this.parameters = parameters || [];
|
this.body = {
|
||||||
|
type: bodyType,
|
||||||
|
code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined,
|
||||||
|
calls: bodyType === FunctionBodyType.Calls ? content as readonly IFunctionCall[] : undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
|||||||
|
|
||||||
public addFunction(func: ISharedFunction): void {
|
public addFunction(func: ISharedFunction): void {
|
||||||
if (!func) { throw new Error('undefined function'); }
|
if (!func) { throw new Error('undefined function'); }
|
||||||
if (this.functionsByName.has(func.name)) {
|
if (this.has(func.name)) {
|
||||||
throw new Error(`function with name ${func.name} already exists`);
|
throw new Error(`function with name ${func.name} already exists`);
|
||||||
}
|
}
|
||||||
this.functionsByName.set(func.name, func);
|
this.functionsByName.set(func.name, func);
|
||||||
@@ -20,4 +20,8 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
|||||||
}
|
}
|
||||||
return func;
|
return func;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private has(functionName: string) {
|
||||||
|
return this.functionsByName.has(functionName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,59 @@
|
|||||||
import { FunctionData, InstructionHolder } from 'js-yaml-loader!*';
|
import { FunctionData, InstructionHolder } from 'js-yaml-loader!@/*';
|
||||||
import { SharedFunction } from './SharedFunction';
|
import { createFunctionWithInlineCode, createCallerFunction } from './SharedFunction';
|
||||||
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
import { SharedFunctionCollection } from './SharedFunctionCollection';
|
||||||
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from './ISharedFunctionCollection';
|
||||||
import { IFunctionCompiler } from './IFunctionCompiler';
|
import { ISharedFunctionsParser } from './ISharedFunctionsParser';
|
||||||
import { IFunctionCallCompiler } from '../FunctionCall/IFunctionCallCompiler';
|
import { FunctionParameter } from './Parameter/FunctionParameter';
|
||||||
import { FunctionCallCompiler } from '../FunctionCall/FunctionCallCompiler';
|
import { FunctionParameterCollection } from './Parameter/FunctionParameterCollection';
|
||||||
|
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||||
|
import { ISharedFunction } from './ISharedFunction';
|
||||||
|
import { parseFunctionCalls } from './Call/FunctionCallParser';
|
||||||
|
|
||||||
export class FunctionCompiler implements IFunctionCompiler {
|
export class SharedFunctionsParser implements ISharedFunctionsParser {
|
||||||
public static readonly instance: IFunctionCompiler = new FunctionCompiler();
|
public static readonly instance: ISharedFunctionsParser = new SharedFunctionsParser();
|
||||||
protected constructor(
|
public parseFunctions(
|
||||||
private readonly functionCallCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance) {
|
functions: readonly FunctionData[]): ISharedFunctionCollection {
|
||||||
}
|
|
||||||
public compileFunctions(functions: readonly FunctionData[]): ISharedFunctionCollection {
|
|
||||||
const collection = new SharedFunctionCollection();
|
const collection = new SharedFunctionCollection();
|
||||||
if (!functions || !functions.length) {
|
if (!functions || !functions.length) {
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
ensureValidFunctions(functions);
|
ensureValidFunctions(functions);
|
||||||
functions
|
for (const func of functions) {
|
||||||
.filter((func) => hasCode(func))
|
const sharedFunction = parseFunction(func);
|
||||||
.forEach((func) => {
|
collection.addFunction(sharedFunction);
|
||||||
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;
|
return collection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseFunction(data: FunctionData): ISharedFunction {
|
||||||
|
const name = data.name;
|
||||||
|
const parameters = parseParameters(data);
|
||||||
|
if (hasCode(data)) {
|
||||||
|
return createFunctionWithInlineCode(name, parameters, data.code, data.revertCode);
|
||||||
|
} else { // has call
|
||||||
|
const calls = parseFunctionCalls(data.call);
|
||||||
|
return createCallerFunction(name, parameters, calls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseParameters(data: FunctionData): IReadOnlyFunctionParameterCollection {
|
||||||
|
const parameters = new FunctionParameterCollection();
|
||||||
|
if (!data.parameters) {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
for (const parameterData of data.parameters) {
|
||||||
|
const isOptional = parameterData.optional || false;
|
||||||
|
try {
|
||||||
|
const parameter = new FunctionParameter(parameterData.name, isOptional);
|
||||||
|
parameters.addParameter(parameter);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`"${data.name}": ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
function hasCode(data: FunctionData): boolean {
|
function hasCode(data: FunctionData): boolean {
|
||||||
return Boolean(data.code);
|
return Boolean(data.code);
|
||||||
}
|
}
|
||||||
@@ -42,13 +62,12 @@ function hasCall(data: FunctionData): boolean {
|
|||||||
return Boolean(data.call);
|
return Boolean(data.call);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
function ensureValidFunctions(functions: readonly FunctionData[]) {
|
||||||
ensureNoUndefinedItem(functions);
|
ensureNoUndefinedItem(functions);
|
||||||
ensureNoDuplicatesInFunctionNames(functions);
|
ensureNoDuplicatesInFunctionNames(functions);
|
||||||
ensureNoDuplicatesInParameterNames(functions);
|
|
||||||
ensureNoDuplicateCode(functions);
|
ensureNoDuplicateCode(functions);
|
||||||
ensureEitherCallOrCodeIsDefined(functions);
|
ensureEitherCallOrCodeIsDefined(functions);
|
||||||
|
ensureExpectedParametersType(functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function printList(list: readonly string[]): string {
|
function printList(list: readonly string[]): string {
|
||||||
@@ -67,6 +86,21 @@ function ensureEitherCallOrCodeIsDefined(holders: readonly InstructionHolder[])
|
|||||||
throw new Error(`neither "code" or "call" is defined in ${printNames(hasEitherCodeOrCall)}`);
|
throw new Error(`neither "code" or "call" is defined in ${printNames(hasEitherCodeOrCall)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureExpectedParametersType(functions: readonly FunctionData[]) {
|
||||||
|
const unexpectedFunctions = functions
|
||||||
|
.filter((func) => func.parameters && !isArrayOfObjects(func.parameters));
|
||||||
|
if (unexpectedFunctions.length) {
|
||||||
|
const errorMessage = `parameters must be an array of objects in function(s) ${printNames(unexpectedFunctions)}`;
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isArrayOfObjects(value: any): boolean {
|
||||||
|
return Array.isArray(value)
|
||||||
|
&& value.every((item) => typeof item === 'object');
|
||||||
|
}
|
||||||
|
|
||||||
function printNames(holders: readonly InstructionHolder[]) {
|
function printNames(holders: readonly InstructionHolder[]) {
|
||||||
return printList(holders.map((holder) => holder.name));
|
return printList(holders.map((holder) => holder.name));
|
||||||
}
|
}
|
||||||
@@ -78,21 +112,13 @@ function ensureNoDuplicatesInFunctionNames(functions: readonly FunctionData[]) {
|
|||||||
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
|
throw new Error(`duplicate function name: ${printList(duplicateFunctionNames)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
|
function ensureNoUndefinedItem(functions: readonly FunctionData[]) {
|
||||||
if (functions.some((func) => !func)) {
|
if (functions.some((func) => !func)) {
|
||||||
throw new Error(`some functions are undefined`);
|
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[]) {
|
function ensureNoDuplicateCode(functions: readonly FunctionData[]) {
|
||||||
const duplicateCodes = getDuplicates(functions
|
const duplicateCodes = getDuplicates(functions
|
||||||
.map((func) => func.code)
|
.map((func) => func.code)
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { ScriptFunctionCallData } from 'js-yaml-loader!*';
|
|
||||||
import { ICompiledCode } from './ICompiledCode';
|
|
||||||
import { ISharedFunctionCollection } from '../Function/ISharedFunctionCollection';
|
|
||||||
|
|
||||||
export interface IFunctionCallCompiler {
|
|
||||||
compileCall(
|
|
||||||
call: ScriptFunctionCallData,
|
|
||||||
functions: ISharedFunctionCollection): ICompiledCode;
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,25 @@
|
|||||||
import { IScriptCode } from '@/domain/IScriptCode';
|
import { IScriptCode } from '@/domain/IScriptCode';
|
||||||
import { ScriptCode } from '@/domain/ScriptCode';
|
import { ScriptCode } from '@/domain/ScriptCode';
|
||||||
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
import { FunctionData, ScriptData } from 'js-yaml-loader!@/*';
|
import { FunctionData, ScriptData } from 'js-yaml-loader!@/*';
|
||||||
import { IScriptCompiler } from './IScriptCompiler';
|
import { IScriptCompiler } from './IScriptCompiler';
|
||||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
|
||||||
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
||||||
import { IFunctionCallCompiler } from './FunctionCall/IFunctionCallCompiler';
|
import { IFunctionCallCompiler } from './Function/Call/Compiler/IFunctionCallCompiler';
|
||||||
import { FunctionCallCompiler } from './FunctionCall/FunctionCallCompiler';
|
import { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
|
||||||
import { IFunctionCompiler } from './Function/IFunctionCompiler';
|
import { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
|
||||||
import { FunctionCompiler } from './Function/FunctionCompiler';
|
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
||||||
|
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
|
||||||
|
|
||||||
export class ScriptCompiler implements IScriptCompiler {
|
export class ScriptCompiler implements IScriptCompiler {
|
||||||
private readonly functions: ISharedFunctionCollection;
|
private readonly functions: ISharedFunctionCollection;
|
||||||
constructor(
|
constructor(
|
||||||
functions: readonly FunctionData[] | undefined,
|
functions: readonly FunctionData[] | undefined,
|
||||||
private readonly syntax: ILanguageSyntax,
|
private readonly syntax: ILanguageSyntax,
|
||||||
functionCompiler: IFunctionCompiler = FunctionCompiler.instance,
|
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
||||||
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
||||||
) {
|
) {
|
||||||
if (!syntax) { throw new Error('undefined syntax'); }
|
if (!syntax) { throw new Error('undefined syntax'); }
|
||||||
this.functions = functionCompiler.compileFunctions(functions);
|
this.functions = sharedFunctionsParser.parseFunctions(functions);
|
||||||
}
|
}
|
||||||
public canCompile(script: ScriptData): boolean {
|
public canCompile(script: ScriptData): boolean {
|
||||||
if (!script) { throw new Error('undefined script'); }
|
if (!script) { throw new Error('undefined script'); }
|
||||||
@@ -30,7 +31,8 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
public compile(script: ScriptData): IScriptCode {
|
public compile(script: ScriptData): IScriptCode {
|
||||||
if (!script) { throw new Error('undefined script'); }
|
if (!script) { throw new Error('undefined script'); }
|
||||||
try {
|
try {
|
||||||
const compiledCode = this.callCompiler.compileCall(script.call, this.functions);
|
const calls = parseFunctionCalls(script.call);
|
||||||
|
const compiledCode = this.callCompiler.compileCall(calls, this.functions);
|
||||||
return new ScriptCode(
|
return new ScriptCode(
|
||||||
compiledCode.code,
|
compiledCode.code,
|
||||||
compiledCode.revertCode,
|
compiledCode.revertCode,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
|
|
||||||
|
|
||||||
|
const BatchFileCommonCodeParts = [ '(', ')', 'else', '||' ];
|
||||||
|
const PowerShellCommonCodeParts = [ '{', '}' ];
|
||||||
|
|
||||||
export class BatchFileSyntax implements ILanguageSyntax {
|
export class BatchFileSyntax implements ILanguageSyntax {
|
||||||
public readonly commentDelimiters = [ 'REM', '::' ];
|
public readonly commentDelimiters = [ 'REM', '::' ];
|
||||||
public readonly commonCodeParts = [ '(', ')', 'else' ];
|
public readonly commonCodeParts = [ ...BatchFileCommonCodeParts, ...PowerShellCommonCodeParts ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
import { IScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/IScriptingLanguageFactory';
|
||||||
|
|
||||||
export interface ISyntaxFactory {
|
export interface ISyntaxFactory extends IScriptingLanguageFactory<ILanguageSyntax> {
|
||||||
create(language: ScriptingLanguage): ILanguageSyntax;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ import { ILanguageSyntax } from '@/domain/ScriptCode';
|
|||||||
|
|
||||||
export class ShellScriptSyntax implements ILanguageSyntax {
|
export class ShellScriptSyntax implements ILanguageSyntax {
|
||||||
public readonly commentDelimiters = [ '#' ];
|
public readonly commentDelimiters = [ '#' ];
|
||||||
public readonly commonCodeParts = [ '(', ')', 'else' ];
|
public readonly commonCodeParts = [ '(', ')', 'else', 'fi' ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
import { ILanguageSyntax } from '@/domain/ScriptCode';
|
||||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
import { ISyntaxFactory } from './ISyntaxFactory';
|
import { ScriptingLanguageFactory } from '@/application/Common/ScriptingLanguage/ScriptingLanguageFactory';
|
||||||
import { BatchFileSyntax } from './BatchFileSyntax';
|
import { BatchFileSyntax } from './BatchFileSyntax';
|
||||||
import { ShellScriptSyntax } from './ShellScriptSyntax';
|
import { ShellScriptSyntax } from './ShellScriptSyntax';
|
||||||
|
import { ISyntaxFactory } from './ISyntaxFactory';
|
||||||
|
|
||||||
export class SyntaxFactory implements ISyntaxFactory {
|
export class SyntaxFactory extends ScriptingLanguageFactory<ILanguageSyntax> implements ISyntaxFactory {
|
||||||
public create(language: ScriptingLanguage): ILanguageSyntax {
|
constructor() {
|
||||||
switch (language) {
|
super();
|
||||||
case ScriptingLanguage.batchfile: return new BatchFileSyntax();
|
this.registerGetter(ScriptingLanguage.batchfile, () => new BatchFileSyntax());
|
||||||
case ScriptingLanguage.shellscript: return new ShellScriptSyntax();
|
this.registerGetter(ScriptingLanguage.shellscript, () => new ShellScriptSyntax());
|
||||||
default: throw new RangeError(`unknown language: "${ScriptingLanguage[language]}"`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||||
|
import { ParameterSubstitutionParser } from '@/application/Parser/Script/Compiler/Expressions/SyntaxParsers/ParameterSubstitutionParser';
|
||||||
|
import { CompositeExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/CompositeExpressionParser';
|
||||||
|
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||||
|
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||||
|
import { ICodeSubstituter } from './ICodeSubstituter';
|
||||||
|
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||||
|
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||||
|
|
||||||
|
export class CodeSubstituter implements ICodeSubstituter {
|
||||||
|
constructor(
|
||||||
|
private readonly compiler: IExpressionsCompiler = createSubstituteCompiler(),
|
||||||
|
private readonly date = new Date(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
public substitute(code: string, info: IProjectInformation): string {
|
||||||
|
if (!code) { throw new Error('undefined code'); }
|
||||||
|
if (!info) { throw new Error('undefined info'); }
|
||||||
|
const args = new FunctionCallArgumentCollection();
|
||||||
|
const substitute = (name: string, value: string) =>
|
||||||
|
args.addArgument(new FunctionCallArgument(name, value));
|
||||||
|
substitute('homepage', info.homepage);
|
||||||
|
substitute('version', info.version);
|
||||||
|
substitute('date', this.date.toUTCString());
|
||||||
|
const compiledCode = this.compiler.compileExpressions(code, args);
|
||||||
|
return compiledCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSubstituteCompiler(): IExpressionsCompiler {
|
||||||
|
const parsers = [ new ParameterSubstitutionParser() ];
|
||||||
|
const parser = new CompositeExpressionParser(parsers);
|
||||||
|
const expressionCompiler = new ExpressionsCompiler(parser);
|
||||||
|
return expressionCompiler;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { IProjectInformation } from '@/domain/IProjectInformation';
|
||||||
|
|
||||||
|
export interface ICodeSubstituter {
|
||||||
|
substitute(code: string, info: IProjectInformation): string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
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 { ICodeSubstituter } from './ICodeSubstituter';
|
||||||
|
import { CodeSubstituter } from './CodeSubstituter';
|
||||||
|
|
||||||
|
export class ScriptingDefinitionParser {
|
||||||
|
constructor(
|
||||||
|
private readonly languageParser = createEnumParser(ScriptingLanguage),
|
||||||
|
private readonly codeSubstituter: ICodeSubstituter = new CodeSubstituter(),
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
public parse(
|
||||||
|
definition: ScriptingDefinitionData,
|
||||||
|
info: IProjectInformation): IScriptingDefinition {
|
||||||
|
if (!info) { throw new Error('undefined info'); }
|
||||||
|
if (!definition) { throw new Error('undefined definition'); }
|
||||||
|
const language = this.languageParser.parseEnum(definition.language, 'language');
|
||||||
|
const startCode = this.codeSubstituter.substitute(definition.startCode, info);
|
||||||
|
const endCode = this.codeSubstituter.substitute(definition.endCode, info);
|
||||||
|
return new ScriptingDefinition(
|
||||||
|
language,
|
||||||
|
startCode,
|
||||||
|
endCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
13
src/application/collections/collection.yaml.d.ts
vendored
13
src/application/collections/collection.yaml.d.ts
vendored
@@ -1,4 +1,4 @@
|
|||||||
declare module 'js-yaml-loader!*' {
|
declare module 'js-yaml-loader!@/*' {
|
||||||
export interface CollectionData {
|
export interface CollectionData {
|
||||||
readonly os: string;
|
readonly os: string;
|
||||||
readonly scripting: ScriptingDefinitionData;
|
readonly scripting: ScriptingDefinitionData;
|
||||||
@@ -24,11 +24,16 @@ declare module 'js-yaml-loader!*' {
|
|||||||
readonly code?: string;
|
readonly code?: string;
|
||||||
readonly revertCode?: string;
|
readonly revertCode?: string;
|
||||||
|
|
||||||
readonly call?: ScriptFunctionCallData;
|
readonly call?: FunctionCallsData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParameterDefinitionData {
|
||||||
|
readonly name: string;
|
||||||
|
readonly optional?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FunctionData extends InstructionHolder {
|
export interface FunctionData extends InstructionHolder {
|
||||||
readonly parameters?: readonly string[];
|
readonly parameters?: readonly ParameterDefinitionData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FunctionCallParametersData {
|
export interface FunctionCallParametersData {
|
||||||
@@ -40,7 +45,7 @@ declare module 'js-yaml-loader!*' {
|
|||||||
readonly parameters?: FunctionCallParametersData;
|
readonly parameters?: FunctionCallParametersData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScriptFunctionCallData = readonly FunctionCallData[] | FunctionCallData | undefined;
|
export type FunctionCallsData = readonly FunctionCallData[] | FunctionCallData | undefined;
|
||||||
|
|
||||||
export interface ScriptData extends InstructionHolder, DocumentableData {
|
export interface ScriptData extends InstructionHolder, DocumentableData {
|
||||||
readonly name: string;
|
readonly name: string;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Structure documented in "docs/collections.md"
|
# Structure documented in "docs/collection-files.md"
|
||||||
os: macos
|
os: macos
|
||||||
scripting:
|
scripting:
|
||||||
language: shellscript
|
language: shellscript
|
||||||
@@ -55,15 +55,81 @@ actions:
|
|||||||
sudo rm -rfv /System/Library/Caches/* &>/dev/null
|
sudo rm -rfv /System/Library/Caches/* &>/dev/null
|
||||||
sudo rm -rfv ~/Library/Caches/* &>/dev/null
|
sudo rm -rfv ~/Library/Caches/* &>/dev/null
|
||||||
-
|
-
|
||||||
name: Clear system log files
|
category: Clear OS logs
|
||||||
recommend: strict
|
recommend: strict
|
||||||
code: |-
|
children:
|
||||||
sudo rm -rfv /private/var/log/asl/*.asl &>/dev/null
|
-
|
||||||
sudo rm -rfv /Library/Logs/DiagnosticReports/* &>/dev/null
|
category: Clear unified logs (diagnostics)
|
||||||
sudo rm -rfv /Library/Logs/Adobe/* &>/dev/null
|
docs: https://developer.apple.com/documentation/os/logging
|
||||||
rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/* &>/dev/null
|
children:
|
||||||
rm -rfv ~/Library/Logs/CoreSimulator/* &>/dev/null
|
-
|
||||||
sudo rm -rfv /var/log/*
|
name: Clear diagnostics logs
|
||||||
|
docs: https://eclecticlight.co/2017/10/10/inside-the-macos-log-logd-and-the-files-that-it-manages/
|
||||||
|
code: |-
|
||||||
|
sudo rm -rfv /private/var/db/diagnostics/*
|
||||||
|
sudo rm -rfv /var/db/diagnostics/*
|
||||||
|
-
|
||||||
|
name: Clear shared-cache strings data
|
||||||
|
docs:
|
||||||
|
- https://eclecticlight.co/2017/09/23/sierras-unified-log-evolves-more-persistent-and-a-valuable-log-log/
|
||||||
|
- https://github.com/privacysexy-forks/dtformats/blob/main/documentation/Apple%20Unified%20Logging%20and%20Activity%20Tracing%20formats.asciidoc
|
||||||
|
code: |-
|
||||||
|
sudo rm -rfv /private/var/db/uuidtext/
|
||||||
|
sudo rm -rfv /var/db/uuidtext/
|
||||||
|
-
|
||||||
|
category: Clear system logs (/var/log/)
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Clear Apple System Logs (ASL)
|
||||||
|
docs:
|
||||||
|
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
||||||
|
- https://apple.stackexchange.com/questions/98197/is-it-safe-to-delete-system-logs
|
||||||
|
code: |-
|
||||||
|
sudo rm -rfv /private/var/log/asl/*
|
||||||
|
sudo rm -rfv /var/log/asl/*
|
||||||
|
sudo rm -fv /var/log/asl.log # Legacy ASL (10.4)
|
||||||
|
sudo rm -fv /var/log/asl.db
|
||||||
|
-
|
||||||
|
name: Clear install logs
|
||||||
|
docs: https://discussions.apple.com/thread/1829842
|
||||||
|
code: sudo rm -fv /var/log/install.log
|
||||||
|
-
|
||||||
|
name: Clear all system logs
|
||||||
|
docs: https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
|
||||||
|
code: sudo rm -rfv /var/log/* # Clears including /var/log/system.log
|
||||||
|
-
|
||||||
|
name: Clear system application logs
|
||||||
|
docs: https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
||||||
|
code: sudo rm -rfv /Library/Logs/*
|
||||||
|
-
|
||||||
|
name: Clear Mail logs
|
||||||
|
code: rm -rfv ~/Library/Containers/com.apple.mail/Data/Library/Logs/Mail/*
|
||||||
|
-
|
||||||
|
name: Clear audit logs (login, logout, authentication and other user activity)
|
||||||
|
docs:
|
||||||
|
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
||||||
|
- http://macadmins.psu.edu/wp-content/uploads/sites/24696/2016/06/psumac2016-19-osxlogs_macadmins_2016.pdf
|
||||||
|
code: |-
|
||||||
|
sudo rm -rfv /var/audit/*
|
||||||
|
sudo rm -rfv /private/var/audit/*
|
||||||
|
-
|
||||||
|
name: Clear user logs (user reports)
|
||||||
|
docs:
|
||||||
|
- https://www.howtogeek.com/356942/how-to-view-the-system-log-on-a-mac/
|
||||||
|
- https://apple.stackexchange.com/questions/272929/is-it-safe-to-delete-the-content-of-library-logs
|
||||||
|
code: sudo rm -rfv ~/Library/Logs/*
|
||||||
|
-
|
||||||
|
name: Clear daily logs
|
||||||
|
docs: https://salt4n6.com/2018/12/11/mac-os-daily-logs/
|
||||||
|
code: sudo rm -fv /System/Library/LaunchDaemons/com.apple.periodic-*.plist
|
||||||
|
-
|
||||||
|
name: Clear receipt logs for installed packages/apps
|
||||||
|
docs:
|
||||||
|
- https://apple.stackexchange.com/questions/327174/whats-the-purpose-of-directory-private-var-db-receipts
|
||||||
|
- https://papers.put.as/papers/macosx/2012/Mac_Log_Analysis_Sarah_Edwards_DFIRSummit2012.pdf
|
||||||
|
code: |-
|
||||||
|
sudo rm -rfv /var/db/receipts/*
|
||||||
|
sudo rm -vf /Library/Receipts/InstallHistory.plist
|
||||||
-
|
-
|
||||||
category: Clear browser history
|
category: Clear browser history
|
||||||
children:
|
children:
|
||||||
@@ -83,41 +149,78 @@ actions:
|
|||||||
children:
|
children:
|
||||||
-
|
-
|
||||||
name: Clear Safari browsing history
|
name: Clear Safari browsing history
|
||||||
|
docs:
|
||||||
|
- https://discussions.apple.com/thread/7586106?answerId=30314600022#30314600022
|
||||||
|
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
code: |-
|
code: |-
|
||||||
rm -f ~/Library/Safari/History.plist
|
rm -f ~/Library/Safari/History.db
|
||||||
rm -f ~/Library/Safari/HistoryIndex.sk
|
rm -f ~/Library/Safari/History.db-lock
|
||||||
|
rm -f ~/Library/Safari/History.db-shm
|
||||||
|
rm -f ~/Library/Safari/History.db-wal
|
||||||
|
# For older versions of Safari
|
||||||
|
rm -f ~/Library/Safari/History.plist # URL, visit count, webpage title, last visited timestamp, redirected URL, autocomplete
|
||||||
|
rm -f ~/Library/Safari/HistoryIndex.sk # History index
|
||||||
-
|
-
|
||||||
name: Clear Safari downloads history
|
name: Clear Safari downloads history
|
||||||
|
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||||
code: rm -f ~/Library/Safari/Downloads.plist
|
code: rm -f ~/Library/Safari/Downloads.plist
|
||||||
-
|
-
|
||||||
name: Clear Safari top sites
|
name: Clear Safari top sites
|
||||||
code: rm -f ~/Library/Safari/TopSites.plist
|
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
|
code: rm -f ~/Library/Safari/TopSites.plist
|
||||||
-
|
-
|
||||||
name: Clear Safari last session history
|
name: Clear Safari last session (open tabs) history
|
||||||
|
docs:
|
||||||
|
- https://apple.stackexchange.com/a/374116
|
||||||
|
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2013-7127
|
||||||
code: rm -f ~/Library/Safari/LastSession.plist
|
code: rm -f ~/Library/Safari/LastSession.plist
|
||||||
-
|
-
|
||||||
name: Clear Safari caches
|
category: Clear Safari caches
|
||||||
code: |-
|
children:
|
||||||
rm -f ~/Library/Caches/com.apple.Safari/Cache.db
|
-
|
||||||
rm -f ~/Library/Safari/WebpageIcons.db
|
name: Clear Safari cached blobs, URLs and timestamps
|
||||||
rm -rf ~/Library/Caches/com.apple.Safari/Webpage Previews
|
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
|
code: rm -f ~/Library/Caches/com.apple.Safari/Cache.db
|
||||||
|
-
|
||||||
|
name: Clear Safari web page icons displayed on URL bar
|
||||||
|
docs:
|
||||||
|
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
|
- https://lifehacker.com/safaris-private-browsing-mode-saves-urls-in-an-easily-a-1691944343
|
||||||
|
code: rm -f ~/Library/Safari/WebpageIcons.db
|
||||||
|
-
|
||||||
|
name: Clear Safari webpage previews (thumbnails)
|
||||||
|
docs:
|
||||||
|
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||||
|
- https://www.reddit.com/r/apple/comments/18lp92/your_apple_computer_keeps_a_screen_shot_of_nearly/
|
||||||
|
code: rm -rfv ~/Library/Caches/com.apple.Safari/Webpage\ Previews
|
||||||
-
|
-
|
||||||
name: Clear copy of the Safari history
|
name: Clear copy of the Safari history
|
||||||
code: rm -rf ~/Library/Caches/Metadata/Safari/History
|
docs: https://forensicsfromthesausagefactory.blogspot.com/2010/06/safari-history-spotlight-webhistory.html
|
||||||
|
code: rm -rfv ~/Library/Caches/Metadata/Safari/History
|
||||||
-
|
-
|
||||||
name: Clear search history embedded in Safari preferences
|
name: Clear search history embedded in Safari preferences
|
||||||
|
docs: https://krypted.com/tag/recentsearchstrings/
|
||||||
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
|
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
|
||||||
-
|
-
|
||||||
name: Clear Safari cookies
|
name: Clear Safari cookies
|
||||||
code: rm -f ~/Library/Cookies/Cookies.plists
|
docs:
|
||||||
|
- https://www.toolbox.com/tech/operating-systems/blogs/understanding-the-safari-cookiesbinarycookies-file-format-010712/
|
||||||
|
- https://link.springer.com/content/pdf/10.1007/0-387-36891-4_13.pdf
|
||||||
|
code: |-
|
||||||
|
rm -f ~/Library/Cookies/Cookies.binarycookies
|
||||||
|
# Used before Safari 5.1
|
||||||
|
rm -f ~/Library/Cookies/Cookies.plist
|
||||||
-
|
-
|
||||||
name: Clear Safari zoom level preferences per site
|
name: Clear Safari zoom level preferences per site
|
||||||
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plists
|
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||||
|
code: rm -f ~/Library/Safari/PerSiteZoomPreferences.plist
|
||||||
-
|
-
|
||||||
name: Clear URLs that are allowed to display notifications in Safari
|
name: Clear URLs that are allowed to display notifications in Safari
|
||||||
|
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||||
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
|
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
|
||||||
-
|
-
|
||||||
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays
|
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays
|
||||||
|
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||||
code: rm -f ~/Library/Safari/PerSitePreferences.db
|
code: rm -f ~/Library/Safari/PerSitePreferences.db
|
||||||
-
|
-
|
||||||
category: Clear Firefox history
|
category: Clear Firefox history
|
||||||
@@ -148,7 +251,7 @@ actions:
|
|||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
|
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
|
||||||
-
|
-
|
||||||
name: Delete Firefox passwords
|
name: Delete Firefox passwords
|
||||||
docs: http://kb.mozillazine.org/Password_Manager
|
docs: https://web.archive.org/web/20210425202923/http://kb.mozillazine.org/Password_Manager
|
||||||
code: |-
|
code: |-
|
||||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
|
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/*/signons2.txt
|
||||||
@@ -355,7 +458,7 @@ actions:
|
|||||||
-
|
-
|
||||||
name: Disable Firefox telemetry
|
name: Disable Firefox telemetry
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs: https://github.com/mozilla/policy-templates/blob/master/README.md
|
docs: https://github.com/privacysexy-forks/policy-templates/blob/master/README.md
|
||||||
code: |-
|
code: |-
|
||||||
# Enable Firefox policies so the telemetry can be configured.
|
# Enable Firefox policies so the telemetry can be configured.
|
||||||
sudo defaults write /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled -bool TRUE
|
sudo defaults write /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled -bool TRUE
|
||||||
@@ -400,7 +503,7 @@ actions:
|
|||||||
-
|
-
|
||||||
name: Disable PowerShell Core telemetry
|
name: Disable PowerShell Core telemetry
|
||||||
recommend: standard
|
recommend: standard
|
||||||
docs: https://github.com/PowerShell/PowerShell/tree/release/v7.1.1#telemetry
|
docs: https://github.com/privacysexy-forks/PowerShell/blob/v7.1.5/README.md#telemetry
|
||||||
call:
|
call:
|
||||||
-
|
-
|
||||||
function: PersistUserEnvironmentConfiguration
|
function: PersistUserEnvironmentConfiguration
|
||||||
@@ -442,46 +545,6 @@ actions:
|
|||||||
recommend: standard
|
recommend: standard
|
||||||
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
|
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
|
||||||
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
|
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
|
name: Do not show recent items on dock
|
||||||
docs: https://developer.apple.com/documentation/devicemanagement/dock
|
docs: https://developer.apple.com/documentation/devicemanagement/dock
|
||||||
@@ -492,10 +555,532 @@ actions:
|
|||||||
recommend: strict
|
recommend: strict
|
||||||
code: defaults write com.apple.NetworkBrowser DisableAirDrop -bool true
|
code: defaults write com.apple.NetworkBrowser DisableAirDrop -bool true
|
||||||
revertCode: defaults write com.apple.NetworkBrowser DisableAirDrop -bool false
|
revertCode: defaults write com.apple.NetworkBrowser DisableAirDrop -bool false
|
||||||
|
-
|
||||||
|
category: Configure Siri
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Opt-out from Siri data collection
|
||||||
|
recommend: standard
|
||||||
|
code: defaults write com.apple.assistant.support 'Siri Data Sharing Opt-In Status' -int 2
|
||||||
|
revertCode: defaults delete com.apple.assistant.support 'Siri Data Sharing Opt-In Status'
|
||||||
|
-
|
||||||
|
category: Disable Siri
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable "Ask Siri"
|
||||||
|
recommend: strict
|
||||||
|
docs: https://derflounder.wordpress.com/2016/09/20/blocking-siri-on-macos-sierra/
|
||||||
|
code: defaults write com.apple.assistant.support 'Assistant Enabled' -bool false
|
||||||
|
revertCode: defaults write com.apple.assistant.support 'Assistant Enabled' -bool true
|
||||||
|
-
|
||||||
|
name: Disable Siri voice feedback
|
||||||
|
recommend: strict
|
||||||
|
docs:
|
||||||
|
- https://github.com/privacysexy-forks/starter/blob/master/system/siri.sh
|
||||||
|
- https://machippie.github.io/system/
|
||||||
|
code: defaults write com.apple.assistant.backedup 'Use device speaker for TTS' -int 3
|
||||||
|
revertCode: defaults write com.apple.assistant.backedup 'Use device speaker for TTS' -int 2
|
||||||
|
-
|
||||||
|
name: Disable Siri services (Siri and assistantd)
|
||||||
|
recommend: strict
|
||||||
|
docs:
|
||||||
|
- https://apple.stackexchange.com/questions/57514/what-is-assistantd
|
||||||
|
- https://www.jamf.com/jamf-nation/discussions/22757/kill-siri#responseChild137563
|
||||||
|
- https://apple.stackexchange.com/a/370426
|
||||||
|
# To see status: • `launchctl print-disabled system` • `launchctl print-disabled user/$UID` • `launchctl print-disabled gui/$UID`
|
||||||
|
code: |-
|
||||||
|
launchctl disable "user/$UID/com.apple.assistantd"
|
||||||
|
launchctl disable "gui/$UID/com.apple.assistantd"
|
||||||
|
sudo launchctl disable 'system/com.apple.assistantd'
|
||||||
|
launchctl disable "user/$UID/com.apple.Siri.agent"
|
||||||
|
launchctl disable "gui/$UID/com.apple.Siri.agent"
|
||||||
|
sudo launchctl disable 'system/com.apple.Siri.agent'
|
||||||
|
if [ $(/usr/bin/csrutil status | awk '/status/ {print $5}' | sed 's/\.$//') = "enabled" ]; then
|
||||||
|
>&2 echo 'This script requires SIP to be disabled. Read more: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection'
|
||||||
|
fi
|
||||||
|
revertCode: |-
|
||||||
|
launchctl enable "user/$UID/com.apple.assistantd"
|
||||||
|
launchctl enable "gui/$UID/com.apple.assistantd"
|
||||||
|
sudo launchctl enable 'system/com.apple.assistantd'
|
||||||
|
launchctl enable "user/$UID/com.apple.Siri.agent"
|
||||||
|
launchctl enable "gui/$UID/com.apple.Siri.agent"
|
||||||
|
sudo launchctl enable 'system/com.apple.Siri.agent'
|
||||||
|
if [ $(/usr/bin/csrutil status | awk '/status/ {print $5}' | sed 's/\.$//') = "enabled" ]; then
|
||||||
|
>&2 echo 'This script requires SIP to be disabled. Read more: https://developer.apple.com/documentation/security/disabling_and_enabling_system_integrity_protection''
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Disable "Do you want to enable Siri?" pop-up
|
||||||
|
docs:
|
||||||
|
- https://discussions.apple.com/thread/7694127?answerId=30752577022#30752577022
|
||||||
|
- https://windowsreport.com/mac/siri-keeps-popping-up/
|
||||||
|
- https://www.jamf.com/jamf-nation/discussions/21783/disable-siri-setup-assistant-in-macos-sierra#responseChild131588
|
||||||
|
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
|
||||||
|
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
|
||||||
|
-
|
||||||
|
category: Hide Siri
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Hide Siri from menu bar
|
||||||
|
recommend: strict
|
||||||
|
code: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 0
|
||||||
|
revertCode: defaults write com.apple.systemuiserver 'NSStatusItem Visible Siri' 1
|
||||||
|
-
|
||||||
|
name: Hide Siri from status menu
|
||||||
|
recommend: strict
|
||||||
|
docs: https://derflounder.wordpress.com/2016/09/20/blocking-siri-on-macos-sierra/
|
||||||
|
code: |-
|
||||||
|
defaults write com.apple.Siri 'StatusMenuVisible' -bool false
|
||||||
|
defaults write com.apple.Siri 'UserHasDeclinedEnable' -bool true
|
||||||
|
revertCode: |-
|
||||||
|
defaults delete com.apple.Siri 'StatusMenuVisible'
|
||||||
|
defaults delete com.apple.Siri 'UserHasDeclinedEnable'
|
||||||
|
-
|
||||||
|
name: Disable Spotlight indexing
|
||||||
|
code: sudo mdutil -i off -d /
|
||||||
|
revertCode: sudo mdutil -i on /
|
||||||
|
-
|
||||||
|
category: Security improvements
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
category: Configure macOS Application Firewall
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Enable application firewall
|
||||||
|
recommend: standard
|
||||||
|
docs:
|
||||||
|
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81681
|
||||||
|
- https://daiderd.com/nix-darwin/manual/index.html
|
||||||
|
- https://developer.apple.com/documentation/devicemanagement/firewall
|
||||||
|
code: |-
|
||||||
|
/usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate on
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.alf globalstate -bool true
|
||||||
|
defaults write com.apple.security.firewall EnableFirewall -bool true
|
||||||
|
revertCode: |-
|
||||||
|
/usr/libexec/ApplicationFirewall/socketfilterfw --setglobalstate off
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.alf globalstate -bool false
|
||||||
|
defaults write com.apple.security.firewall EnableFirewall -bool false
|
||||||
|
-
|
||||||
|
name: Turn on firewall logging
|
||||||
|
recommend: standard
|
||||||
|
docs:
|
||||||
|
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
|
||||||
|
- https://daiderd.com/nix-darwin/manual/index.html
|
||||||
|
code: |-
|
||||||
|
/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode on
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -bool true
|
||||||
|
revertCode: |-
|
||||||
|
/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -bool false
|
||||||
|
-
|
||||||
|
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
|
||||||
|
- https://daiderd.com/nix-darwin/manual/index.html
|
||||||
|
- https://developer.apple.com/documentation/devicemanagement/firewall
|
||||||
|
code: |-
|
||||||
|
/usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode on
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -bool true
|
||||||
|
defaults write com.apple.security.firewall EnableStealthMode -bool true
|
||||||
|
revertCode: |-
|
||||||
|
/usr/libexec/ApplicationFirewall/socketfilterfw --setstealthmode off
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.alf stealthenabled -bool false
|
||||||
|
defaults write com.apple.security.firewall EnableStealthMode -bool false
|
||||||
|
-
|
||||||
|
category: Disable auto-permitting incoming traffic for apps
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Prevent automatically allowing incoming connections to signed apps
|
||||||
|
docs: https://daiderd.com/nix-darwin/manual/index.html
|
||||||
|
recommend: strict
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool false
|
||||||
|
revertCode: sudo defaults write /Library/Preferences/com.apple.alf allowsignedenabled -bool true
|
||||||
|
-
|
||||||
|
name: Prevent automatically allowing incoming connections to downloaded signed apps
|
||||||
|
docs: https://daiderd.com/nix-darwin/manual/index.html
|
||||||
|
recommend: strict
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsignedenabled -bool false
|
||||||
|
revertCode: sudo defaults write /Library/Preferences/com.apple.alf allowdownloadsignedenabled -bool true
|
||||||
|
-
|
||||||
|
name: Disable Captive portal
|
||||||
|
# An attacker could trigger the utility and direct a Mac to a site with malware without user interaction,
|
||||||
|
# so it's best to disable this feature and log in to captive portals using regular Web browser instead.
|
||||||
|
recommend: standard
|
||||||
|
docs:
|
||||||
|
# Risks with captive portals:
|
||||||
|
- https://www.eff.org/deeplinks/2017/08/how-captive-portals-interfere-wireless-security-and-privacy
|
||||||
|
# More about apple Captive portal:
|
||||||
|
- 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
|
||||||
|
-
|
||||||
|
category: Use screen saver for protection
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Require a password to wake the computer from sleep or screen saver
|
||||||
|
# The screen saver acts as a session lock and prevents unauthorized users from accessing the current user's account.
|
||||||
|
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230744
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.screensaver askForPassword -bool true
|
||||||
|
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver askForPassword
|
||||||
|
-
|
||||||
|
name: Initiate session lock five seconds after screen saver is started
|
||||||
|
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2020-11-27/finding/V-230745
|
||||||
|
# An unattended system with an excessive grace period is vulnerable to a malicious user.
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.screensaver 'askForPasswordDelay' -int 5
|
||||||
|
revertCode: sudo defaults delete /Library/Preferences/com.apple.screensaver 'askForPasswordDelay'
|
||||||
|
-
|
||||||
|
category: Disable guest accounts
|
||||||
|
docs:
|
||||||
|
- https://www.stigviewer.com/stig/apple_macos_11_big_sur/2021-06-16/finding/V-230823
|
||||||
|
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81615
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disables signing in as Guest from the login screen
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool NO
|
||||||
|
revetCode: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool YES
|
||||||
|
-
|
||||||
|
name: Disables Guest access to file shares over AF
|
||||||
|
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool NO
|
||||||
|
revetCode: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool YES
|
||||||
|
-
|
||||||
|
name: Disables Guest access to file shares over SMB
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool NO
|
||||||
|
revetCode: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool YES
|
||||||
|
-
|
||||||
|
category: Prevent unauthorized connections
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable remote login (incoming SSH and SFTP connections)
|
||||||
|
recommend: standard
|
||||||
|
docs: https://osxdaily.com/2016/08/16/enable-ssh-mac-command-line/
|
||||||
|
# Check if enabled: sudo systemsetup -getremotelogin, returns "Remote Login: On" or "Off"
|
||||||
|
code: echo 'yes' | sudo systemsetup -setremotelogin off
|
||||||
|
revertCode: sudo systemsetup -setremotelogin on
|
||||||
|
-
|
||||||
|
name: Disable insecure TFTP service
|
||||||
|
recommend: standard
|
||||||
|
# If the system does not require Trivial File Transfer Protocol (TFTP), then support for
|
||||||
|
# it is non-essential and should be disabled. The information system should be configured to
|
||||||
|
# provide only essential capabilities. Disabling TFTP helps prevent the unauthorized connection
|
||||||
|
# of devices and the unauthorized transfer of information.
|
||||||
|
docs: https://www.stigviewer.com/stig/apple_macos_11_big_sur/2021-06-16/finding/V-230813
|
||||||
|
code: sudo launchctl disable 'system/com.apple.tftpd'
|
||||||
|
revertCode: sudo launchctl enable 'system/com.apple.tftpd'
|
||||||
|
-
|
||||||
|
name: Disable Bonjour multicast advertising
|
||||||
|
recommend: standard
|
||||||
|
docs: https://www.stigviewer.com/stig/apple_os_x_10.11/2017-04-06/finding/V-67593
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.mDNSResponder.plist NoMulticastAdvertisements -bool true
|
||||||
|
revertCode: sudo defaults write /Library/Preferences/com.apple.mDNSResponder.plist NoMulticastAdvertisements -bool false
|
||||||
|
-
|
||||||
|
name: Disable insecure telnet protocol
|
||||||
|
recommend: standard
|
||||||
|
docs:
|
||||||
|
- https://www.stigviewer.com/stig/apple_os_x_10.13/2020-09-11/finding/V-214882
|
||||||
|
- https://www.stigviewer.com/stig/apple_os_x_10.10_yosemite_workstation/2017-04-06/finding/V-59671
|
||||||
|
code: sudo launchctl disable system/com.apple.telnetd
|
||||||
|
revertCode: sudo launchctl enable system/com.apple.telnetd
|
||||||
|
-
|
||||||
|
category: Disable printer sharing (IPP, LDP, SMB and Bonjour protocols)
|
||||||
|
# Used typically for servers
|
||||||
|
# By default, the CUPS only listens to requests from the machine that it's running on
|
||||||
|
# cupsctl is a tool to manage the configuration of the CUPS daemon
|
||||||
|
docs:
|
||||||
|
- https://www.cups.org/doc/sharing.html
|
||||||
|
- https://www.cups.org/doc/security.html # Security risks
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable sharing of local printers with other computers
|
||||||
|
recommend: standard
|
||||||
|
docs: https://www.cups.org/doc/man-cupsctl.html
|
||||||
|
code: cupsctl --no-share-printers
|
||||||
|
revertCode: cupsctl --share-printers
|
||||||
|
-
|
||||||
|
name: Disable printing from any address including the Internet
|
||||||
|
recommend: standard
|
||||||
|
docs: https://www.cups.org/doc/man-cupsctl.html
|
||||||
|
code: cupsctl --no-remote-any
|
||||||
|
revertCode: cupsctl --remote-any
|
||||||
|
-
|
||||||
|
name: Disable remote printer administration
|
||||||
|
recommend: standard
|
||||||
|
docs: https://www.cups.org/doc/man-cupsctl.html
|
||||||
|
code: cupsctl --no-remote-admin
|
||||||
|
revertCode: cupsctl --remote-admin
|
||||||
|
-
|
||||||
|
category: Privacy over security
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
category: Disable File Quarantine (tracks downloaded files and warns)
|
||||||
|
# OS tracks downloaded files with help of quarantine-aware applications
|
||||||
|
# (such as Safari, Chrome) adding quarantine extended attributes to files.
|
||||||
|
# then OS warns and asks if you really want to open it
|
||||||
|
docs: https://support.apple.com/en-gb/HT202491
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
category: Clean File Quarantine from downloaded files
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Clear File Quarantine logs of all downloaded files
|
||||||
|
recommend: strict
|
||||||
|
docs:
|
||||||
|
- https://www.macobserver.com/tips/how-to/your-mac-remembers-everything-you-download-heres-how-to-clear-download-history/
|
||||||
|
- https://eclecticlight.co/2019/04/25/%F0%9F%8E%97-quarantine-apps/
|
||||||
|
- https://eclecticlight.co/2017/12/11/xattr-com-apple-quarantine-the-quarantine-flag/
|
||||||
|
- https://eclecticlight.co/2017/08/14/show-me-your-metadata-extended-attributes-in-macos-sierra/
|
||||||
|
# Query entries using:
|
||||||
|
# sqlite3 ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 'select DISTINCT LSQuarantineDataURLString from LSQuarantineEvent'
|
||||||
|
code: |-
|
||||||
|
db_file=~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2
|
||||||
|
db_query='delete from LSQuarantineEvent'
|
||||||
|
if [ -f "$db_file" ]; then
|
||||||
|
echo "Database exists at \"$db_file\""
|
||||||
|
if ls -lO "$db_file" | grep --silent 'schg'; then
|
||||||
|
sudo chflags noschg "$db_file"
|
||||||
|
echo "Found and removed system immutable flag"
|
||||||
|
has_sytem_immutable_flag=true
|
||||||
|
fi
|
||||||
|
if ls -lO "$db_file" | grep --silent 'uchg'; then
|
||||||
|
sudo chflags nouchg "$db_file"
|
||||||
|
echo "Found and removed user immutable flag"
|
||||||
|
has_user_immutable_flag=true
|
||||||
|
fi
|
||||||
|
sqlite3 "$db_file" "$db_query"
|
||||||
|
echo "Executed the query \"$db_query\""
|
||||||
|
if [ "$has_sytem_immutable_flag" = true ] ; then
|
||||||
|
sudo chflags schg "$db_file"
|
||||||
|
echo "Added system immutable flag back"
|
||||||
|
fi
|
||||||
|
if [ "$has_user_immutable_flag" = true ] ; then
|
||||||
|
sudo chflags uchg "$db_file"
|
||||||
|
echo "Added user immutable flag back"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No action needed, database does not exist at \"$db_file\""
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Clear File Quarantine attribute from downloaded files
|
||||||
|
docs: https://superuser.com/questions/28384/what-should-i-do-about-com-apple-quarantine
|
||||||
|
code: |-
|
||||||
|
find ~/Downloads \
|
||||||
|
-type f \
|
||||||
|
-exec \
|
||||||
|
sh -c \
|
||||||
|
'
|
||||||
|
attr="com.apple.quarantine"
|
||||||
|
file="{}"
|
||||||
|
if [[ $(xattr "$file") = *$attr* ]]; then
|
||||||
|
if xattr -d "$attr" "$file" 2>/dev/null; then
|
||||||
|
echo "🧹 Cleaned attribute from \"$file\""
|
||||||
|
else
|
||||||
|
>&2 echo "❌ Failed to clean attribute from \"$file\""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "No attribute in \"$file\""
|
||||||
|
fi
|
||||||
|
' \
|
||||||
|
{} \;
|
||||||
|
-
|
||||||
|
category: Disable File Quarantine from tracking downloaded files
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Prevent quarantine from logging downloaded files
|
||||||
|
docs:
|
||||||
|
- https://eclecticlight.co/2019/04/25/%F0%9F%8E%97-quarantine-apps/
|
||||||
|
- https://eclecticlight.co/2017/12/11/xattr-com-apple-quarantine-the-quarantine-flag/
|
||||||
|
- https://eclecticlight.co/2017/08/14/show-me-your-metadata-extended-attributes-in-macos-sierra/
|
||||||
|
recommend: strict
|
||||||
|
code: |-
|
||||||
|
file_to_lock=~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2
|
||||||
|
if [ -f "$file_to_lock" ]; then
|
||||||
|
sudo chflags schg "$file_to_lock"
|
||||||
|
echo "Made file immutable at \"$file_to_lock\""
|
||||||
|
else
|
||||||
|
echo "No action is needed, file does not exist at \"$file_to_lock\""
|
||||||
|
fi
|
||||||
|
revertCode: |-
|
||||||
|
file_to_lock=~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2
|
||||||
|
if [ -f "$file_to_lock" ]; then
|
||||||
|
sudo chflags noschg "$file_to_lock"
|
||||||
|
echo "Successfully reverted immutability from \"$file_to_lock\""
|
||||||
|
else
|
||||||
|
>&2 echo "Cannot revert immutability, file does not exist at\"$file_to_lock\""
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Disable using extended quarantine attribute on downloaded files (disables warning)
|
||||||
|
# Disables dialogs shown when opening an application for the first time
|
||||||
|
# i.e. "Application Downloaded from Internet" quarantine warning.
|
||||||
|
docs:
|
||||||
|
- https://apple.stackexchange.com/questions/373176/disable-the-use-of-the-com-apple-quarantine-extended-attribute-on-mojave
|
||||||
|
- https://superuser.com/questions/266176/is-there-some-way-to-disable-the-dialogs-shown-when-opening-an-application-for-t
|
||||||
|
- https://macos-defaults.com/misc/lsquarantine.html
|
||||||
|
code: sudo defaults write com.apple.LaunchServices 'LSQuarantine' -bool NO
|
||||||
|
revertCode: sudo defaults delete com.apple.LaunchServices 'LSQuarantine'
|
||||||
|
-
|
||||||
|
category: Disable Gatekeeper (enforces code-signing)
|
||||||
|
# Built on top of File Quarantine, requires code-signing for apps.
|
||||||
|
# Warns user if a file is not signed by it's developer with certificate issued by Apple.
|
||||||
|
# Can protect against unknown threats.
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Prevent Gatekeeper from automatically reactivating itself
|
||||||
|
docs:
|
||||||
|
- https://osxdaily.com/2015/11/05/stop-gatekeeper-auto-rearm-mac-os-x/
|
||||||
|
- https://www.cnet.com/tech/computing/how-to-disable-gatekeeper-permanently-on-os-x/
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.security GKAutoRearm -bool true
|
||||||
|
revertCode: sudo defaults write /Library/Preferences/com.apple.security GKAutoRearm -bool false
|
||||||
|
-
|
||||||
|
name: Disable Gatekeeper
|
||||||
|
docs:
|
||||||
|
# References for spctl --master-disable
|
||||||
|
- https://www.manpagez.com/man/8/spctl/
|
||||||
|
# References for /var/db/SystemPolicy-prefs.plist
|
||||||
|
- https://krypted.com/mac-security/manage-gatekeeper-from-the-command-line-in-mountain-lion/
|
||||||
|
- https://community.jamf.com/t5/jamf-pro/users-can-t-change-password-greyed-out/m-p/54228
|
||||||
|
code: |-
|
||||||
|
os_major_ver=$(sw_vers -productVersion | awk -F "." '{print $1}')
|
||||||
|
os_minor_ver=$(sw_vers -productVersion | awk -F "." '{print $2}')
|
||||||
|
if [[ $os_major_ver -le 10 \
|
||||||
|
|| ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \
|
||||||
|
]]; then
|
||||||
|
echo "No action needed, Gatekeeper is not available this OS version"
|
||||||
|
else
|
||||||
|
gatekeeper_status="$(spctl --status | awk '/assessments/ {print $2}')"
|
||||||
|
if [ $gatekeeper_status = "disabled" ]; then
|
||||||
|
echo "No action needed, Gatekeeper is already disabled"
|
||||||
|
elif [ $gatekeeper_status = "enabled" ]; then
|
||||||
|
sudo spctl --master-disable
|
||||||
|
sudo defaults write '/var/db/SystemPolicy-prefs' 'enabled' -string 'no'
|
||||||
|
echo "Disabled Gatekeeper"
|
||||||
|
else
|
||||||
|
>&2 echo "Unknown gatekeeper status: $gatekeeper_status"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
revertCode: |-
|
||||||
|
os_major_ver=$(sw_vers -productVersion | awk -F "." '{print $1}')
|
||||||
|
os_minor_ver=$(sw_vers -productVersion | awk -F "." '{print $2}')
|
||||||
|
if [[ $os_major_ver -le 10 \
|
||||||
|
|| ( $os_major_ver -eq 10 && $os_minor_ver -lt 7 ) \
|
||||||
|
]]; then
|
||||||
|
>&2 echo "Gatekeeper is not available in this OS version"
|
||||||
|
else
|
||||||
|
gatekeeper_status="$(spctl --status | awk '/assessments/ {print $2}')"
|
||||||
|
if [ $gatekeeper_status = "disabled" ]; then
|
||||||
|
sudo spctl --master-enable
|
||||||
|
sudo defaults write '/var/db/SystemPolicy-prefs' 'enabled' -string 'yes'
|
||||||
|
echo "Enabled Gatekeeper"
|
||||||
|
elif [ $gatekeeper_status = "enabled" ]; then
|
||||||
|
echo "No action needed, Gatekeeper is already enabled"
|
||||||
|
else
|
||||||
|
>&2 echo "Unknown Gatekeeper status: $gatekeeper_status"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
-
|
||||||
|
name: Disable Library Validation Entitlement (checks signature of libraries)
|
||||||
|
docs:
|
||||||
|
- https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_cs_disable-library-validation
|
||||||
|
- https://www.macenhance.com/docs/general/sip-library-validation.html
|
||||||
|
- https://www.naut.ca/blog/2020/11/13/forbidden-commands-to-liberate-macos/
|
||||||
|
code: sudo defaults write /Library/Preferences/com.apple.security.libraryvalidation.plist 'DisableLibraryValidation' -bool true
|
||||||
|
revertCode: sudo defaults write /Library/Preferences/com.apple.security.libraryvalidation.plist 'DisableLibraryValidation' -bool false
|
||||||
|
-
|
||||||
|
category: Disable automatic updates
|
||||||
|
docs:
|
||||||
|
- https://developer.apple.com/documentation/devicemanagement/deviceinformationresponse/queryresponses/osupdatesettings
|
||||||
|
- https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html
|
||||||
|
children:
|
||||||
|
-
|
||||||
|
name: Disable automatically checking for updates
|
||||||
|
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
|
code: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
|
||||||
|
revertCode: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
|
||||||
|
-
|
||||||
|
name: Disable automatically downloading new updates when available
|
||||||
|
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
|
code: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
|
||||||
|
revertCode: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
|
||||||
|
-
|
||||||
|
name: Disable automatically installing macOS updates
|
||||||
|
docs:
|
||||||
|
# References for AutoUpdateRestartRequired
|
||||||
|
- https://kb.vmware.com/s/article/2960635
|
||||||
|
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||||
|
# References for AutomaticallyInstallMacOSUpdates
|
||||||
|
- https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
|
code: |-
|
||||||
|
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool false
|
||||||
|
# For Mojave and later (>= 10.14)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool false
|
||||||
|
revertCode: |-
|
||||||
|
# For OS X Yosemite through macOS High Sierra (>= 10.10 && < 10.14)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdateRestartRequired' -bool true
|
||||||
|
# For Mojave and later (>= 10.14)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
|
||||||
|
-
|
||||||
|
name: Disable automatically updating app from the App Store
|
||||||
|
docs:
|
||||||
|
- https://kb.vmware.com/s/article/2960635
|
||||||
|
- https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||||
|
code: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
|
||||||
|
# For Mojave and later (>= 10.14)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool false
|
||||||
|
revertCode: |-
|
||||||
|
# For OS X Yosemite and later
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool true
|
||||||
|
# For Mojave and later (>= 10.14)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
|
||||||
|
-
|
||||||
|
name: Disable installation of macOS beta releases
|
||||||
|
docs: https://support.apple.com/en-gb/HT203018
|
||||||
|
code: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
|
||||||
|
revertCode: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
|
||||||
|
-
|
||||||
|
name: Disable automatically installing configuration data (e.g. XProtect, Gatekeeper, MRT)
|
||||||
|
docs: https://derflounder.wordpress.com/2018/12/28/enabling-automatic-macos-software-updates-for-os-x-yosemite-through-macos-mojave/
|
||||||
|
code: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
|
||||||
|
revertCode: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
|
||||||
|
-
|
||||||
|
name: Disable automatically installing system data files and security updates
|
||||||
|
docs:
|
||||||
|
# References for CriticalUpdateInstall
|
||||||
|
- https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/
|
||||||
|
- https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||||
|
# References for softwareupdate --background-critical
|
||||||
|
- https://managingosx.wordpress.com/2013/04/30/undocumented-options/
|
||||||
|
code: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
|
||||||
|
revertCode: |-
|
||||||
|
# For OS X Yosemite and later (>= 10.10)
|
||||||
|
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool true
|
||||||
|
# Trigger background check with normal scan (critical updates only)
|
||||||
|
sudo softwareupdate --background-critical
|
||||||
functions:
|
functions:
|
||||||
-
|
-
|
||||||
name: PersistUserEnvironmentConfiguration
|
name: PersistUserEnvironmentConfiguration
|
||||||
parameters: [ configuration ]
|
parameters:
|
||||||
|
- name: configuration
|
||||||
code: |-
|
code: |-
|
||||||
command='{{ $configuration }}'
|
command='{{ $configuration }}'
|
||||||
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
|
declare -a profile_files=("$HOME/.bash_profile" "$HOME/.zprofile")
|
||||||
@@ -520,4 +1105,4 @@ functions:
|
|||||||
else
|
else
|
||||||
echo "[$profile_file] No need for any action, configuration does not exist"
|
echo "[$profile_file] No need for any action, configuration does not exist"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
|||||||
import { getEnumNames, getEnumValues } from '@/application/Common/Enum';
|
import { getEnumNames, getEnumValues, assertInRange } from '@/application/Common/Enum';
|
||||||
import { IEntity } from '../infrastructure/Entity/IEntity';
|
import { IEntity } from '../infrastructure/Entity/IEntity';
|
||||||
import { ICategory } from './ICategory';
|
import { ICategory } from './ICategory';
|
||||||
import { IScript } from './IScript';
|
import { IScript } from './IScript';
|
||||||
@@ -21,7 +21,7 @@ export class CategoryCollection implements ICategoryCollection {
|
|||||||
throw new Error('undefined scripting definition');
|
throw new Error('undefined scripting definition');
|
||||||
}
|
}
|
||||||
this.queryable = makeQueryable(actions);
|
this.queryable = makeQueryable(actions);
|
||||||
ensureValidOs(os);
|
assertInRange(os, OperatingSystem);
|
||||||
ensureValid(this.queryable);
|
ensureValid(this.queryable);
|
||||||
ensureNoDuplicates(this.queryable.allCategories);
|
ensureNoDuplicates(this.queryable.allCategories);
|
||||||
ensureNoDuplicates(this.queryable.allScripts);
|
ensureNoDuplicates(this.queryable.allScripts);
|
||||||
@@ -54,18 +54,6 @@ export class CategoryCollection implements ICategoryCollection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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>>) {
|
function ensureNoDuplicates<TKey>(entities: ReadonlyArray<IEntity<TKey>>) {
|
||||||
const totalOccurrencesById = new Map<TKey, number>();
|
const totalOccurrencesById = new Map<TKey, number>();
|
||||||
for (const entity of entities) {
|
for (const entity of entities) {
|
||||||
|
|||||||
@@ -10,5 +10,4 @@ export enum OperatingSystem {
|
|||||||
Android,
|
Android,
|
||||||
iOS,
|
iOS,
|
||||||
WindowsPhone,
|
WindowsPhone,
|
||||||
Unknown,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { IProjectInformation } from './IProjectInformation';
|
import { IProjectInformation } from './IProjectInformation';
|
||||||
import { OperatingSystem } from './OperatingSystem';
|
import { OperatingSystem } from './OperatingSystem';
|
||||||
|
import { assertInRange } from '@/application/Common/Enum';
|
||||||
|
|
||||||
export class ProjectInformation implements IProjectInformation {
|
export class ProjectInformation implements IProjectInformation {
|
||||||
public readonly repositoryWebUrl: string;
|
public readonly repositoryWebUrl: string;
|
||||||
@@ -42,6 +43,7 @@ function getWebUrl(gitUrl: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getFileName(os: OperatingSystem, version: string): string {
|
function getFileName(os: OperatingSystem, version: string): string {
|
||||||
|
assertInRange(os, OperatingSystem);
|
||||||
switch (os) {
|
switch (os) {
|
||||||
case OperatingSystem.Linux:
|
case OperatingSystem.Linux:
|
||||||
return `privacy.sexy-${version}.AppImage`;
|
return `privacy.sexy-${version}.AppImage`;
|
||||||
@@ -50,6 +52,6 @@ function getFileName(os: OperatingSystem, version: string): string {
|
|||||||
case OperatingSystem.Windows:
|
case OperatingSystem.Windows:
|
||||||
return `privacy.sexy-Setup-${version}.exe`;
|
return `privacy.sexy-Setup-${version}.exe`;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported os: ${OperatingSystem[os]}`);
|
throw new RangeError(`Unsupported os: ${OperatingSystem[os]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,7 @@ export class ScriptCode implements IScriptCode {
|
|||||||
syntax: ILanguageSyntax) {
|
syntax: ILanguageSyntax) {
|
||||||
if (!syntax) { throw new Error('undefined syntax'); }
|
if (!syntax) { throw new Error('undefined syntax'); }
|
||||||
validateCode(execute, syntax);
|
validateCode(execute, syntax);
|
||||||
if (revert) {
|
validateRevertCode(revert, execute, syntax);
|
||||||
try {
|
|
||||||
validateCode(revert, syntax);
|
|
||||||
if (execute === revert) {
|
|
||||||
throw new Error(`Code itself and its reverting code cannot be the same`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
throw Error(`(revert): ${err.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +16,20 @@ export interface ILanguageSyntax {
|
|||||||
readonly commonCodeParts: string[];
|
readonly commonCodeParts: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function validateRevertCode(revertCode: string, execute: string, syntax: ILanguageSyntax) {
|
||||||
|
if (!revertCode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
validateCode(revertCode, syntax);
|
||||||
|
if (execute === revertCode) {
|
||||||
|
throw new Error(`Code itself and its reverting code cannot be the same`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw Error(`(revert): ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function validateCode(code: string, syntax: ILanguageSyntax): void {
|
function validateCode(code: string, syntax: ILanguageSyntax): void {
|
||||||
if (!code || code.length === 0) {
|
if (!code || code.length === 0) {
|
||||||
throw new Error(`code is empty or undefined`);
|
throw new Error(`code is empty or undefined`);
|
||||||
@@ -34,23 +39,37 @@ function validateCode(code: string, syntax: ILanguageSyntax): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ensureNoEmptyLines(code: string): void {
|
function ensureNoEmptyLines(code: string): void {
|
||||||
if (code.split('\n').some((line) => line.trim().length === 0)) {
|
const lines = code.split(/\r\n|\r|\n/);
|
||||||
throw Error(`script has empty lines`);
|
if (lines.some((line) => line.trim().length === 0)) {
|
||||||
|
throw Error(`Script has empty lines:\n${lines.map((part, index) => `\n (${index}) ${part || '❌'}`).join('')}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureCodeHasUniqueLines(code: string, syntax: ILanguageSyntax): void {
|
function ensureCodeHasUniqueLines(code: string, syntax: ILanguageSyntax): void {
|
||||||
const lines = code.split('\n')
|
const allLines = code.split(/\r\n|\r|\n/);
|
||||||
.filter((line) => !shouldIgnoreLine(line, syntax));
|
const checkedLines = allLines.filter((line) => !shouldIgnoreLine(line, syntax));
|
||||||
if (lines.length === 0) {
|
if (checkedLines.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const duplicateLines = lines.filter((e, i, a) => a.indexOf(e) !== i);
|
const duplicateLines = checkedLines.filter((e, i, a) => a.indexOf(e) !== i);
|
||||||
if (duplicateLines.length !== 0) {
|
if (duplicateLines.length !== 0) {
|
||||||
throw Error(`Duplicates detected in script :\n ${duplicateLines.join('\n')}`);
|
throw Error(`Duplicates detected in script:\n${printDuplicatedLines(allLines)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function printDuplicatedLines(allLines: string[]) {
|
||||||
|
return allLines
|
||||||
|
.map((line, index) => {
|
||||||
|
const occurrenceIndices = allLines
|
||||||
|
.map((e, i) => e === line ? i : '')
|
||||||
|
.filter(String);
|
||||||
|
const isDuplicate = occurrenceIndices.length > 1;
|
||||||
|
const indicator = isDuplicate ? `❌ (${occurrenceIndices.join(',')})\t` : '✅ ';
|
||||||
|
return `${indicator}[${index}] ${line}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
function shouldIgnoreLine(codeLine: string, syntax: ILanguageSyntax): boolean {
|
function shouldIgnoreLine(codeLine: string, syntax: ILanguageSyntax): boolean {
|
||||||
codeLine = codeLine.toLowerCase();
|
codeLine = codeLine.toLowerCase();
|
||||||
const isCommentLine = () => syntax.commentDelimiters.some((delimiter) => codeLine.startsWith(delimiter));
|
const isCommentLine = () => syntax.commentDelimiters.some((delimiter) => codeLine.startsWith(delimiter));
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user