Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af7219f6e1 | ||
|
|
8ccaec7af6 | ||
|
|
b2ffc90da7 | ||
|
|
72e4d0b896 | ||
|
|
5bb13e34f8 | ||
|
|
0466b86f10 | ||
|
|
ca81f68ff1 | ||
|
|
4995e49c46 | ||
|
|
77123d8c92 | ||
|
|
e72c1c13ea | ||
|
|
e775d68a9b | ||
|
|
f8e5f1a5a2 | ||
|
|
f4a74f058d | ||
|
|
80821fca07 | ||
|
|
dfd4451561 | ||
|
|
8570b02dde | ||
|
|
d6da406c61 | ||
|
|
060e789662 | ||
|
|
e40b9a3cf5 | ||
|
|
237d9944f9 | ||
|
|
79b46bf210 | ||
|
|
98a26f9ae4 | ||
|
|
dbe3c5cfb9 | ||
|
|
25d7f7b2a4 | ||
|
|
b76e99ac0f | ||
|
|
67c3677621 | ||
|
|
bab6316e76 | ||
|
|
48730bca05 | ||
|
|
698b570ee6 | ||
|
|
a3f11dff18 | ||
|
|
5e359c2fb8 | ||
|
|
2147eae687 | ||
|
|
286295128d | ||
|
|
8501495c17 | ||
|
|
888c9166fc | ||
|
|
e5f6edf405 | ||
|
|
e8a52f717d | ||
|
|
d45750428c | ||
|
|
cf55ca9e28 | ||
|
|
3e5239f7d3 | ||
|
|
7669985f8e | ||
|
|
5047c9b6e7 | ||
|
|
bd2082e8c5 | ||
|
|
8f188acd3c | ||
|
|
0303ef2fd9 | ||
|
|
cb21a970b6 | ||
|
|
203daeb4a2 | ||
|
|
60dde11311 | ||
|
|
8b930fc57c | ||
|
|
f810ed0c14 | ||
|
|
53222fd83c | ||
|
|
a1f2497381 | ||
|
|
c27172c32e | ||
|
|
6e9b65d8b1 | ||
|
|
6d301f9961 | ||
|
|
659fea7afc | ||
|
|
e0303058a3 |
@@ -5,3 +5,7 @@ end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 100
|
||||
|
||||
[{Dockerfile}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
@@ -8,4 +8,5 @@ runs:
|
||||
-
|
||||
name: Run `npm ci` with retries
|
||||
shell: bash
|
||||
run: npm run install-deps -- --ci --root-directory "${{ inputs.working-directory }}"
|
||||
run: npm run install-deps -- --ci
|
||||
working-directory: ${{ inputs.working-directory }}
|
||||
|
||||
2
.github/actions/setup-node/action.yml
vendored
@@ -3,6 +3,6 @@ runs:
|
||||
steps:
|
||||
-
|
||||
name: Setup node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
36
.github/workflows/checks.build.yaml
vendored
@@ -1,4 +1,4 @@
|
||||
name: build-checks
|
||||
name: checks.build
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -68,3 +68,33 @@ jobs:
|
||||
-
|
||||
name: Verify bundled desktop build artifacts
|
||||
run: npm run check:verify-build-artifacts -- --electron-bundled
|
||||
|
||||
build-docker:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos, ubuntu ] # Windows runners do not support Linux containers
|
||||
fail-fast: false # Allows to see results from other combinations
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Install Docker on macOS
|
||||
if: matrix.os == 'macos' # macOS runner is missing Docker
|
||||
run: |-
|
||||
# Install Docker
|
||||
brew install docker
|
||||
# Docker on macOS misses daemon due to licensing, so install colima as runtime
|
||||
brew install colima
|
||||
# Start the daemon
|
||||
colima start
|
||||
-
|
||||
name: Build Docker image
|
||||
run: docker build -t undergroundwires/privacy.sexy:latest .
|
||||
-
|
||||
name: Run Docker image on port 8080
|
||||
run: docker run -d -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest
|
||||
-
|
||||
name: Check server is up and returns HTTP 200
|
||||
run: node ./scripts/verify-web-server-status.js --url http://localhost:8080
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
2
.github/workflows/checks.external-urls.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
2
.github/workflows/checks.quality.yaml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
4
.github/workflows/checks.scripts.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: security-checks
|
||||
name: checks.security.dependencies
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
42
.github/workflows/checks.security.sast.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: checks.security.sast
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0' # at 00:00 on every Sunday
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [
|
||||
javascript # analyzes code written in JavaScript, TypeScript and both.
|
||||
]
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-and-quality
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
2
.github/workflows/release.desktop.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
steps:
|
||||
-
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master # otherwise it defaults to the version tag missing bump commit
|
||||
fetch-depth: 0 # fetch all history
|
||||
|
||||
8
.github/workflows/release.site.yaml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: "Infrastructure: Checkout"
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: aws
|
||||
repository: undergroundwires/aws-static-site-with-cd
|
||||
@@ -75,7 +75,7 @@ jobs:
|
||||
working-directory: aws
|
||||
-
|
||||
name: "App: Checkout"
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: app
|
||||
ref: master # otherwise we don't get version bump commit
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
uses: ./app/.github/actions/setup-node
|
||||
-
|
||||
name: "App: Install dependencies"
|
||||
uses: ./.github/actions/npm-install-dependencies
|
||||
uses: ./app/.github/actions/npm-install-dependencies
|
||||
with:
|
||||
working-directory: app
|
||||
-
|
||||
@@ -102,7 +102,7 @@ jobs:
|
||||
-
|
||||
name: "App: Deploy to S3"
|
||||
shell: bash
|
||||
run: >-
|
||||
run: |-
|
||||
declare web_output_dir
|
||||
if ! web_output_dir=$(cd app && node scripts/print-dist-dir.js --web); then
|
||||
echo 'Error: Could not determine distribution directory.'
|
||||
|
||||
2
.github/workflows/tests.e2e.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
2
.github/workflows/tests.integration.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Setup node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
2
.github/workflows/tests.unit.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set-up node
|
||||
uses: ./.github/actions/setup-node
|
||||
|
||||
81
CHANGELOG.md
@@ -1,5 +1,86 @@
|
||||
# Changelog
|
||||
|
||||
## 0.12.6 (2023-11-03)
|
||||
|
||||
* Bump dependencies to latest | [25d7f7b](https://github.com/undergroundwires/privacy.sexy/commit/25d7f7b2a479e51e092881cc2751e67a7d3f179f)
|
||||
* win: improve system app uninstall cleanup #73 | [dbe3c5c](https://github.com/undergroundwires/privacy.sexy/commit/dbe3c5cfb91ba8a1657838b69117858843c8fbc8)
|
||||
* win: improve system app uninstall /w fallback #260 | [98a26f9](https://github.com/undergroundwires/privacy.sexy/commit/98a26f9ae47af2668aa53f39d1768983036048ce)
|
||||
* Improve performance of rendering during search | [79b46bf](https://github.com/undergroundwires/privacy.sexy/commit/79b46bf21004d96d31551439e5db5d698a3f71f3)
|
||||
* Fix YAML error for site release in CI/CD | [237d994](https://github.com/undergroundwires/privacy.sexy/commit/237d9944f900f5172366868d75219224ff0542b0)
|
||||
* win: fix Microsoft Advertising app removal #200 | [e40b9a3](https://github.com/undergroundwires/privacy.sexy/commit/e40b9a3cf53c341f2e84023a9f0e9680ac08f3fa)
|
||||
* win: improve directory cleanup security | [060e789](https://github.com/undergroundwires/privacy.sexy/commit/060e7896624309aebd25e8b190c127282de177e8)
|
||||
* Centralize Electron entry file path configuration | [d6da406](https://github.com/undergroundwires/privacy.sexy/commit/d6da406c61e5b9f5408851d1302d6d7398157a2e)
|
||||
* win: prevent updates from reinstalling apps #260 | [8570b02](https://github.com/undergroundwires/privacy.sexy/commit/8570b02dde14ffad64863f614682c3fc1f87b6c2)
|
||||
* win: improve script environment robustness #221 | [dfd4451](https://github.com/undergroundwires/privacy.sexy/commit/dfd44515613f38abe5a806bda36f44e7b715b50b)
|
||||
* Fix compiler failing with nested `with` expression | [80821fc](https://github.com/undergroundwires/privacy.sexy/commit/80821fca0769e5fd2c6338918fbdcea12fbe83d2)
|
||||
* win: improve soft file/app delete security #260 | [f4a74f0](https://github.com/undergroundwires/privacy.sexy/commit/f4a74f058db9b5bcbcbe438785db5ec88ecc1657)
|
||||
* Fix incorrect tooltip position after window resize | [f8e5f1a](https://github.com/undergroundwires/privacy.sexy/commit/f8e5f1a5a2afa1f18567e6d965359b6a1f082367)
|
||||
* linux: fix string formatting of Firefox configs | [e775d68](https://github.com/undergroundwires/privacy.sexy/commit/e775d68a9b4a5f9e893ff0e3500dade036185193)
|
||||
* win: improve file delete | [e72c1c1](https://github.com/undergroundwires/privacy.sexy/commit/e72c1c13ea2d73ebfc7a8da5a21254fdfc0e5b59)
|
||||
* win: change system app removal to hard delete #260 | [77123d8](https://github.com/undergroundwires/privacy.sexy/commit/77123d8c929d23676a9cb21d7b697703fd1b6e82)
|
||||
* Improve UI performance by optimizing reactivity | [4995e49](https://github.com/undergroundwires/privacy.sexy/commit/4995e49c469211404dac9fcb79b75eb121f80bce)
|
||||
* Migrate to Vue 3.0 #230 | [ca81f68](https://github.com/undergroundwires/privacy.sexy/commit/ca81f68ff1c3bbe5b22981096ae9220b0b5851c7)
|
||||
* win, linux: unify & improve Firefox clean-up #273 | [0466b86](https://github.com/undergroundwires/privacy.sexy/commit/0466b86f1013341c966a9bbf6513990337b16598)
|
||||
* win: fix store revert for multiple installs #260 | [5bb13e3](https://github.com/undergroundwires/privacy.sexy/commit/5bb13e34f8de2e2a7ba943ff72b12c0569435e62)
|
||||
|
||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.5...0.12.6)
|
||||
|
||||
## 0.12.5 (2023-10-13)
|
||||
|
||||
* Fix Docker build and improve checks #220 | [7669985](https://github.com/undergroundwires/privacy.sexy/commit/7669985f8e1446e726a95626ecf35b3ce6b60a16)
|
||||
* Add SAST security checks with SECURITY.md #178 | [3e5239f](https://github.com/undergroundwires/privacy.sexy/commit/3e5239f7d35e57749c01adf3dbbcd365aebb39c8)
|
||||
* Add Scoop download instructions #174 | [cf55ca9](https://github.com/undergroundwires/privacy.sexy/commit/cf55ca9e28b064fa7a516077a9da23e3a8e3f534)
|
||||
* win: fix and improve temp dir cleanup #176, #89 | [d457504](https://github.com/undergroundwires/privacy.sexy/commit/d45750428cca010daf2721b33a8ae3a01b28813b)
|
||||
* win, linux: improve VSCode setting robustness #196 | [e8a52f7](https://github.com/undergroundwires/privacy.sexy/commit/e8a52f717dc799b34ceeb1c27c2b8219391dff6a)
|
||||
* linux: fix obsolete Firefox DPI script #239 | [e5f6edf](https://github.com/undergroundwires/privacy.sexy/commit/e5f6edf405bcec7c29ea4d7932d1910620fa15f8)
|
||||
* win: add removal of Edge assocations #64 | [888c916](https://github.com/undergroundwires/privacy.sexy/commit/888c9166fc66a2094137fa8be739cc21bafef5f6)
|
||||
* win: improve Edge & OneDrive shortcut removal #73 | [8501495](https://github.com/undergroundwires/privacy.sexy/commit/8501495c170af61913288a63dbd369db5bbc5003)
|
||||
* win: relocate and document SecHealthUI #190 | [2862951](https://github.com/undergroundwires/privacy.sexy/commit/286295128d0179358e0c6b7b6415d752175a1aed)
|
||||
* Add developer toolkit UI component | [2147eae](https://github.com/undergroundwires/privacy.sexy/commit/2147eae687b82d05bc43bb4605d9068f148bb92a)
|
||||
* win: fix and improve network data usage reset #265 | [5e359c2](https://github.com/undergroundwires/privacy.sexy/commit/5e359c2fb82a08e6acf7159b70ca86a8234b359b)
|
||||
* win: improve app reversion and docs #260 | [a3f11df](https://github.com/undergroundwires/privacy.sexy/commit/a3f11dff187c821a00910c20dac05e285cda9073)
|
||||
* Fix working directory in CI/CD web release | [698b570](https://github.com/undergroundwires/privacy.sexy/commit/698b570ee6e300d6703015464f4345b5e706f1cb)
|
||||
* Implement new UI component for icons #230 | [48730bc](https://github.com/undergroundwires/privacy.sexy/commit/48730bca0506120bca4bf3a23545d59f2b1a9009)
|
||||
* win: fix and improve AppCompat disabling #255 | [bab6316](https://github.com/undergroundwires/privacy.sexy/commit/bab6316e7625230cf4a4cf67c3aca417347db75c)
|
||||
* win, linux, mac: fix typos and improve naming | [67c3677](https://github.com/undergroundwires/privacy.sexy/commit/67c3677621b201525a813e8a26f07d607176e89b)
|
||||
|
||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.4...0.12.5)
|
||||
|
||||
## 0.12.4 (2023-09-25)
|
||||
|
||||
* win: fix Windows spotlight revert, docs, recommend | [659fea7](https://github.com/undergroundwires/privacy.sexy/commit/659fea7afcabcd0ea273cfdcc8c4bae190c126f3)
|
||||
* win: fix Edge telemetry disabling for v116+ #242 | [6d301f9](https://github.com/undergroundwires/privacy.sexy/commit/6d301f99616ed49975876803d0098eafe4d3cb2e)
|
||||
* win: fix, improve disabling automatic updates #252 | [6e9b65d](https://github.com/undergroundwires/privacy.sexy/commit/6e9b65d8b1b481c1471dde90876c37838b4ac4e5)
|
||||
* win: refactor `update.mode` key for VSCode #215 | [c27172c](https://github.com/undergroundwires/privacy.sexy/commit/c27172c32e7c316b7cb0f44cab611eed89ca034e)
|
||||
* Fix wrong action path in website CI deployment | [a1f2497](https://github.com/undergroundwires/privacy.sexy/commit/a1f24973813ccbdd7e1f06c64e1912a991a6bb64)
|
||||
* Fix compiler bug with nested optional arguments | [53222fd](https://github.com/undergroundwires/privacy.sexy/commit/53222fd83c2846089746a217482195806f960d18)
|
||||
* Fix no spacing after lists in documentation text | [f810ed0](https://github.com/undergroundwires/privacy.sexy/commit/f810ed0c147c2a46cae3b70b635ed81128646fff)
|
||||
* Rewrite tooltip UI for efficiency and Vue 3.0 #230 | [8b930fc](https://github.com/undergroundwires/privacy.sexy/commit/8b930fc57c8ee6691ed6165bcb27d97e64a1a0c0)
|
||||
* win: fix uninstallation of newer Edge #236 | [60dde11](https://github.com/undergroundwires/privacy.sexy/commit/60dde11311a2409537f5965f370b0daaaec53339)
|
||||
* win: fix delivery optimization side-effects #173 | [203daeb](https://github.com/undergroundwires/privacy.sexy/commit/203daeb4a2fca0a0295cbc2a736394f9f87725e6)
|
||||
* win: fix Defender scan artifacts removal #246 | [cb21a97](https://github.com/undergroundwires/privacy.sexy/commit/cb21a970b6b867e1476a5eb8a72b9a7fdd53a744)
|
||||
* Fix outdated and broken links in README #161 | [0303ef2](https://github.com/undergroundwires/privacy.sexy/commit/0303ef2fd98b36306523e2a0c5f5ae812a4c6c99)
|
||||
* Fix loss of tree node state when switching views | [8f188ac](https://github.com/undergroundwires/privacy.sexy/commit/8f188acd3c2d93e40c89569c74bc5cff992f0052)
|
||||
* Fix slow appearance of nodes on tree view | [bd2082e](https://github.com/undergroundwires/privacy.sexy/commit/bd2082e8c574db065bb4462f30ea3ace2cb028cb)
|
||||
|
||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.3...0.12.4)
|
||||
|
||||
## 0.12.3 (2023-09-09)
|
||||
|
||||
* linux: use user.js over prefs.js for Firefox #232 | [dae6d11](https://github.com/undergroundwires/privacy.sexy/commit/dae6d114daab6857d773071211eb57619b136281)
|
||||
* win: fix typo in Defender retention script #213 | [35be05d](https://github.com/undergroundwires/privacy.sexy/commit/35be05df2094ea8bba4ee4725e6fa4956a79493d)
|
||||
* Improve desktop runtime execution tests | [ad0576a](https://github.com/undergroundwires/privacy.sexy/commit/ad0576a752f8fd6ea2f917a59173fe61f9951246)
|
||||
* Fix Windows artifact naming in desktop packaging | [f4d86fc](https://github.com/undergroundwires/privacy.sexy/commit/f4d86fccfd0e73e94c8c6e400a33514900bc5abe)
|
||||
* Refactor and improve external URL checks | [19e42c9](https://github.com/undergroundwires/privacy.sexy/commit/19e42c9c52a18c813ded4265e687e01032cdd4c8)
|
||||
* Fix memory leaks via auto-unsubscribing and DI | [eb096d0](https://github.com/undergroundwires/privacy.sexy/commit/eb096d07e276e1b4c8040220c47f186d02841e14)
|
||||
* Refactor build configs and improve CI/CD checks | [0a2a1a0](https://github.com/undergroundwires/privacy.sexy/commit/0a2a1a026b0efb29624be82b06536c518c1ea439)
|
||||
* Introduce retry mechanism for npm install in CI/CD | [4beb1bb](https://github.com/undergroundwires/privacy.sexy/commit/4beb1bb5748a60886210187ca3cdc7f4b41067c0)
|
||||
* win: fix disable recent apps revert #211, #248 | [4ce327e](https://github.com/undergroundwires/privacy.sexy/commit/4ce327eb6af542ed2916d649553e5e1ba5833882)
|
||||
* Change license to AGPLv3 | [821cc62](https://github.com/undergroundwires/privacy.sexy/commit/821cc62c4c8347cb76d041f82f574754e4d948c5)
|
||||
* Introduce new TreeView UI component | [65f121c](https://github.com/undergroundwires/privacy.sexy/commit/65f121c451af87315e1c91df4198562e0445b2c2)
|
||||
|
||||
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.12.2...0.12.3)
|
||||
|
||||
## 0.12.2 (2023-08-25)
|
||||
|
||||
* Add automated checks for desktop app runtime #233 | [04b3133](https://github.com/undergroundwires/privacy.sexy/commit/04b3133500485d0d278a81a177a1677134131405)
|
||||
|
||||
@@ -43,6 +43,7 @@ You have two alternatives:
|
||||
|
||||
1. [Create an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) and ask for someone else to add the script for you.
|
||||
2. Or send a PR yourself. This would make it faster to get your code into the project. You need to add scripts to related OS in [collections](src/application/collections/) folder. Then you'd sent a pull request, see [pull request process](#pull-request-process).
|
||||
- 💡 You should use existing shared functions for most of the operations, like `DisableService` for disabling services, to maintain code consistency and efficiency.
|
||||
- 📖 If you're unsure about the syntax, check [collection-files.md](docs/collection-files.md).
|
||||
- 📖 If you wish to use templates, use [templating.md](./docs/templating.md).
|
||||
|
||||
|
||||
17
Dockerfile
@@ -1,13 +1,16 @@
|
||||
# Build
|
||||
FROM node:lts-alpine as build-stage
|
||||
FROM node:lts-alpine AS build-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
RUN npm run install-deps
|
||||
RUN npm run build \
|
||||
&& npm run check:verify-build-artifacts -- --web
|
||||
RUN mkdir /dist \
|
||||
&& dist_directory=$(node 'scripts/print-dist-dir.js' --web) \
|
||||
&& cp -a "${dist_directory}/." '/dist'
|
||||
|
||||
# Production stage
|
||||
FROM nginx:stable-alpine as production-stage
|
||||
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||
FROM nginx:stable-alpine AS production-stage
|
||||
COPY --from=build-stage /dist /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
57
README.md
@@ -16,14 +16,6 @@
|
||||
src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"
|
||||
/>
|
||||
</a>
|
||||
<!-- Code quality -->
|
||||
<br />
|
||||
<a href="https://lgtm.com/projects/g/undergroundwires/privacy.sexy/context:javascript" target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
alt="Language grade: JavaScript/TypeScript"
|
||||
src="https://img.shields.io/lgtm/grade/javascript/g/undergroundwires/privacy.sexy.svg?logo=lgtm&logoWidth=18"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://codeclimate.com/github/undergroundwires/privacy.sexy/maintainability" target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
alt="Maintainability"
|
||||
@@ -50,6 +42,20 @@
|
||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/e2e-tests/badge.svg"
|
||||
/>
|
||||
</a>
|
||||
<!-- Security checks -->
|
||||
<br />
|
||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.sast.yaml" target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
alt="Status of dependency security checks"
|
||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.sast/badge.svg"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.dependencies.yaml" target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
alt="Status of Static Analysis Security Testing (SAST)"
|
||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.security.dependencies/badge.svg"
|
||||
/>
|
||||
</a>
|
||||
<!-- Checks -->
|
||||
<br />
|
||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.quality.yaml" target="_blank" rel="noopener noreferrer">
|
||||
@@ -58,16 +64,10 @@
|
||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/quality-checks/badge.svg"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.security.yaml" target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
alt="Security checks status"
|
||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/security-checks/badge.svg"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.build.yaml" target="_blank" rel="noopener noreferrer">
|
||||
<img
|
||||
alt="Build checks status"
|
||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/build-checks/badge.svg"
|
||||
alt="Status of build checks"
|
||||
src="https://github.com/undergroundwires/privacy.sexy/workflows/checks.build/badge.svg"
|
||||
/>
|
||||
</a>
|
||||
<a href="https://github.com/undergroundwires/privacy.sexy/actions/workflows/checks.desktop-runtime-errors.yaml" target="_blank" rel="noopener noreferrer">
|
||||
@@ -122,7 +122,7 @@
|
||||
## Get started
|
||||
|
||||
- 🌍️ **Online**: [https://privacy.sexy](https://privacy.sexy).
|
||||
- 🖥️ **Offline**: Check [releases page](https://github.com/undergroundwires/privacy.sexy/releases), or download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-Setup-0.11.2.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.dmg), [Linux](https://github.com/undergroundwires/pr.vacy.sexy/releases/download/0.11.2/privacy.sexy-0.11.2.AppImage).
|
||||
- 🖥️ **Offline**: Download directly for: [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.6/privacy.sexy-Setup-0.12.6.exe), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.6/privacy.sexy-0.12.6.dmg), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.12.6/privacy.sexy-0.12.6.AppImage). For more options, see [here](#additional-install-options).
|
||||
|
||||
Online version does not require to run any software on your computer. Offline version has more functions such as running the scripts directly.
|
||||
|
||||
@@ -150,6 +150,25 @@ Online version does not require to run any software on your computer. Offline ve
|
||||
|
||||
**Contribute 👷**. Contributions of any type are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) as the starting point. It includes useful information like [how to add new scripts](./CONTRIBUTING.md#extend-scripts).
|
||||
|
||||
## Additional Install Options
|
||||
|
||||
- Check the [releases page](https://github.com/undergroundwires/privacy.sexy/releases) for all available versions.
|
||||
- Other unofficial channels (not maintained by privacy.sexy) for Windows include:
|
||||
- [Scoop 🥄](https://scoop.sh/#/apps?q=privacy.sexy&s=2&d=1&o=true) (latest version):
|
||||
|
||||
```powershell
|
||||
scoop bucket add extras
|
||||
scoop install privacy.sexy
|
||||
```
|
||||
|
||||
- [winget 🪟](https://winget.run/pkg/undergroundwires/privacy.sexy) (may be outdated):
|
||||
|
||||
```powershell
|
||||
winget install -e --id undergroundwires.privacy.sexy
|
||||
```
|
||||
|
||||
With winget, updates require manual submission; the auto-update feature within privacy.sexy will notify you of new releases post-installation.
|
||||
|
||||
## Development
|
||||
|
||||
Refer to [development.md](./docs/development.md) for Docker usage and reading more about setting up your development environment.
|
||||
@@ -157,3 +176,7 @@ Refer to [development.md](./docs/development.md) for Docker usage and reading mo
|
||||
Check [architecture.md](./docs/architecture.md) for an overview of design and how different parts and layers work together. You can refer to [application.md](./docs/application.md) for a closer look at application layer codebase and [presentation.md](./docs/presentation.md) for code related to GUI layer. [collection-files.md](./docs/collection-files.md) explains the YAML files that are the core of the application and [templating.md](./docs/templating.md) documents how to use templating language in those files. In [ci-cd.md](./docs/ci-cd.md), you can read more about the pipelines that automates maintenance tasks and ensures you get what see.
|
||||
|
||||
[docs/](./docs/) folder includes all other documentation.
|
||||
|
||||
## Security
|
||||
|
||||
Security is a top priority at privacy.sexy. An extensive commitment to security verification ensures this priority. For any security concerns or vulnerabilities, please consult the [Security Policy](./SECURITY.md).
|
||||
|
||||
31
SECURITY.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Security Policy
|
||||
|
||||
privacy.sexy takes security seriously. Commitment is made to address all security issues with urgency. Responsible reporting of any discovered vulnerabilities in the project is highly encouraged.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Efforts to responsibly disclose findings are greatly appreciated. To report a security vulnerability, follow these steps:
|
||||
|
||||
- For general vulnerabilities, [open an issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose) using the bug report template.
|
||||
- For sensitive matters, [contact the developer directly](https://undergroundwires.dev).
|
||||
|
||||
## Security Report Handling
|
||||
|
||||
Upon receipt of a security report, the following actions will be taken:
|
||||
|
||||
- The report will be confirmed, identifying the affected components.
|
||||
- The impact and severity of the issue will be assessed.
|
||||
- Work on a fix and plan a release to address the vulnerability will be initiated.
|
||||
- The reporter will be kept updated about the progress.
|
||||
|
||||
## Testing
|
||||
|
||||
Regular and extensive testing is conducted to ensure robust security in the project. Information about testing practices can be found in the [Testing Documentation](./docs/tests.md).
|
||||
|
||||
## Support
|
||||
|
||||
For additional assistance or any unanswered questions, [submit a GitHub issue](https://github.com/undergroundwires/privacy.sexy/issues/new/choose). Security concerns are a priority, and necessary support to address them is assured.
|
||||
|
||||
---
|
||||
|
||||
Active contribution to the safety and security of privacy.sexy is thanked. This collaborative effort keeps the project resilient and trustworthy for all.
|
||||
@@ -6,7 +6,10 @@ const CYPRESS_BASE_DIR = 'tests/e2e/';
|
||||
export default defineConfig({
|
||||
fixturesFolder: `${CYPRESS_BASE_DIR}/fixtures`,
|
||||
screenshotsFolder: `${CYPRESS_BASE_DIR}/screenshots`,
|
||||
|
||||
video: true,
|
||||
videosFolder: `${CYPRESS_BASE_DIR}/videos`,
|
||||
|
||||
e2e: {
|
||||
baseUrl: `http://localhost:${ViteConfig.server.port}/`,
|
||||
specPattern: `${CYPRESS_BASE_DIR}/**/*.cy.{js,jsx,ts,tsx}`, // Default: cypress/e2e/**/*.cy.{js,jsx,ts,tsx}
|
||||
|
||||
@@ -174,3 +174,19 @@
|
||||
- `endCode:` *`string`* (**required**)
|
||||
- Code that'll be inserted at the end of user created script.
|
||||
- Global variables such as `$homepage`, `$version`, `$date` can be used using [parameter substitution](./templating.md#parameter-substitution) code syntax such as `Welcome to {{ $homepage }}!`
|
||||
|
||||
## Naming guidelines
|
||||
|
||||
- Prioritize consistency throughout all names.
|
||||
- Use an instruction format like "do this, do that" for clear, direct guidance. This approach reduces potential confusion and offers easy-to-follow steps. It provides specific, unambiguous instructions.
|
||||
- Ensure brand names adhere to their official casing.
|
||||
- Choose clear and uncomplicated language.
|
||||
- Favor the terms:
|
||||
- "Disable" over "Turn off"
|
||||
- "Configure" over "Set up"
|
||||
- "Clear" over "Erase" or "Clean"
|
||||
- "Minimize" over "Limit" or "Reduce" (when it enhances clarity)
|
||||
- "Remove" over "Uninstall"
|
||||
- Structure your phrases for clarity.
|
||||
- For instance, "Disable XX telemetry" or "Clear XX data" are preferred over "Clear data from XX", "Disable telemetry in XX", or "Clear data of XX".
|
||||
- Use sentence case rather than Title Case.
|
||||
|
||||
@@ -60,6 +60,7 @@ See [ci-cd.md](./ci-cd.md) for more information.
|
||||
|
||||
1. Build: `docker build -t undergroundwires/privacy.sexy:latest .`
|
||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy undergroundwires/privacy.sexy:latest`
|
||||
3. Application should be available at [`http://localhost:8080`](http://localhost:8080)
|
||||
|
||||
### Building
|
||||
|
||||
@@ -81,11 +82,12 @@ See [ci-cd.md](./ci-cd.md) for more information.
|
||||
|
||||
#### Automation scripts
|
||||
|
||||
- [**`node scripts/print-dist-dir.js [-- <options>]`**](../scripts/print-dist-dir.js):
|
||||
- [**`node scripts/print-dist-dir.js [<options>]`**](../scripts/print-dist-dir.js):
|
||||
- Determines the absolute path of a distribution directory based on CLI arguments and outputs its absolute path.
|
||||
- Primarily used by automation scripts.
|
||||
- [**`npm run check:verify-build-artifacts [-- <options>]`**](../scripts/verify-build-artifacts.js):
|
||||
- Verifies the existence and content of build artifacts. Useful for ensuring that the build process is generating the expected output.
|
||||
- [**`node scripts/verify-web-server-status.js --url [URL]`**](../scripts/verify-web-server-status.js):
|
||||
- Checks if a specified server is up with retries and returns an HTTP 200 status code.
|
||||
|
||||
## Recommended extensions
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ The presentation layer uses an event-driven architecture for bidirectional react
|
||||
- [**`fonts/`**](./../src/presentation/assets/fonts/): Contains fonts.
|
||||
- [**`styles/`**](./../src/presentation/assets/styles/): Contains shared styles.
|
||||
- [**`components/`**](./../src/presentation/assets/styles/components): Contains styles coupled to Vue components.
|
||||
- [**`vendors-extensions/`**](./../src/presentation/assets/styles/third-party-extensions): Contains styles for third-party components.
|
||||
- [**`main.scss`**](./../src/presentation/assets/styles/main.scss): Main Sass file, imported by other components as single entrypoint.
|
||||
- [**`main.ts`**](./../src/presentation/main.ts): Starts Vue app.
|
||||
- [**`electron/`**](./../src/presentation/electron/): Contains Electron code.
|
||||
|
||||
@@ -2,79 +2,142 @@
|
||||
|
||||
## Benefits of templating
|
||||
|
||||
- Generating scripts by sharing code to increase best-practice usage and maintainability.
|
||||
- Creating self-contained scripts without cross-dependencies.
|
||||
- Use of pipes for writing cleaner code and letting pipes do dirty work.
|
||||
- **Code sharing:** Share code across scripts for consistent practices and easier maintenance.
|
||||
- **Script independence:** Generate self-contained scripts, eliminating the need for external code.
|
||||
- **Cleaner code:** Use pipes for complex operations, resulting in more readable and streamlined code.
|
||||
|
||||
## Expressions
|
||||
|
||||
- Expressions start and end with mustaches (double brackets, `{{` and `}}`).
|
||||
- E.g. `Hello {{ $name }} !`
|
||||
- Syntax is close to [Go Templates ❤️](https://pkg.go.dev/text/template) but not the same.
|
||||
- Functions enables usage of expressions.
|
||||
- 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).
|
||||
- Expressions inside expressions (nested templates) are supported.
|
||||
- An expression can output another expression that will also be compiled.
|
||||
- E.g. following would compile first [with expression](#with), and then [parameter substitution](#parameter-substitution) in its output.
|
||||
**Syntax:**
|
||||
|
||||
```go
|
||||
{{ with $condition }}
|
||||
echo {{ $text }}
|
||||
{{ end }}
|
||||
```
|
||||
Expressions are enclosed within `{{` and `}}`.
|
||||
Example: `Hello {{ $name }}!`.
|
||||
They are a core component of templating, enhancing scripts with dynamic capabilities and functionality.
|
||||
|
||||
**Syntax similarity:**
|
||||
|
||||
The syntax shares similarities with [Go Templates ❤️](https://pkg.go.dev/text/template), but with some differences:
|
||||
|
||||
**Function definitions:**
|
||||
|
||||
You can use expressions in function definition.
|
||||
Refer to [Function](./collection-files.md#function) for more details.
|
||||
|
||||
Example usage:
|
||||
|
||||
```yaml
|
||||
name: GreetFunction
|
||||
parameters:
|
||||
- name: name
|
||||
code: Hello {{ $name }}!
|
||||
```
|
||||
|
||||
If you assign `name` the value `world`, invoking `GreetFunction` would result in `Hello world!`.
|
||||
|
||||
**Function arguments:**
|
||||
|
||||
You can also use expressions in arguments in nested function calls.
|
||||
Refer to [`Function | collection-files.md`](./collection-files.md#functioncall) for more details.
|
||||
|
||||
Example with nested function calls:
|
||||
|
||||
```yaml
|
||||
-
|
||||
name: PrintMessageFunction
|
||||
parameters:
|
||||
- name: message
|
||||
code: echo "{{ $message }}"
|
||||
-
|
||||
name: GreetUserFunction
|
||||
parameters:
|
||||
- name: userName
|
||||
call:
|
||||
name: PrintMessageFunction
|
||||
parameters:
|
||||
argument: 'Hello, {{ $userName }}!'
|
||||
```
|
||||
|
||||
Here, if `userName` is `Alice`, invoking `GreetUserFunction` would execute `echo "Hello, Alice!"`.
|
||||
|
||||
**Nested templates:**
|
||||
|
||||
You can nest expressions inside expressions (also called "nested templates").
|
||||
This means that an expression can output another expression where compiler will compile both.
|
||||
|
||||
For example, following would compile first [with expression](#with), and then [parameter substitution](#parameter-substitution) in its output:
|
||||
|
||||
```go
|
||||
{{ with $condition }}
|
||||
echo {{ $text }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Parameter substitution
|
||||
|
||||
A simple function example:
|
||||
Parameter substitution dynamically replaces variable references with their corresponding values in the script.
|
||||
|
||||
**Example function:**
|
||||
|
||||
```yaml
|
||||
function: EchoArgument
|
||||
name: DisplayTextFunction
|
||||
parameters:
|
||||
- name: 'argument'
|
||||
code: Hello {{ $argument }} !
|
||||
- name: 'text'
|
||||
code: echo {{ $text }}
|
||||
```
|
||||
|
||||
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 }} !
|
||||
```
|
||||
Invoking `DisplayTextFunction` with `text` set to `"Hello, world!"` would result in `echo "Hello, World!"`.
|
||||
|
||||
### with
|
||||
|
||||
Skips its "block" if the variable is absent or empty. Its "block" is between `with` start (`{{ with .. }}`) and end (`{{ end }`}) expressions.
|
||||
E.g. `{{ with $parameterName }} Hi, I'm a block! {{ end }}` would only output `Hi, I'm a block!` if `parameterName` has any value..
|
||||
The `with` expression enables conditional rendering and provides a context variable for simpler code.
|
||||
|
||||
It binds its context (value of the provided parameter value) as arbitrary `.` value. It allows you to use the argument value of the given parameter when it is provided and not empty such as:
|
||||
**Optional block rendering:**
|
||||
|
||||
If the provided variable is falsy (`false`, `null`, or empty), the compiler skips the enclosed block of code.
|
||||
A "block" lies between the with start (`{{ with .. }}`) and end (`{{ end }}`) expressions, defining its boundaries.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
{{ with $optionalVariable }}
|
||||
Hello
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
This would display `Hello` if `$optionalVariable` is truthy.
|
||||
|
||||
**Parameter declaration:**
|
||||
|
||||
You should set `optional: true` for the argument if you use it like `{{ with $argument }} .. {{ end }}`.
|
||||
|
||||
Declare parameters used for `with` condition as optional such as:
|
||||
|
||||
```yaml
|
||||
name: ConditionalOutputFunction
|
||||
parameters:
|
||||
- name: 'data'
|
||||
optional: true
|
||||
code: |-
|
||||
{{ with $data }}
|
||||
Data is: {{ . }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
**Context variable:**
|
||||
|
||||
`with` statement binds its context (value of the provided parameter value) as arbitrary `.` value.
|
||||
`{{ . }}` syntax gives you access to the context variable.
|
||||
This is optional to use, and not required to use `with` expressions.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
{{ with $parameterName }}Parameter value is {{ . }} here {{ end }}
|
||||
```
|
||||
|
||||
It supports multiline text inside the block. You can have something like:
|
||||
**Multiline text:**
|
||||
|
||||
It supports multiline text inside the block. You can write something like:
|
||||
|
||||
```go
|
||||
{{ with $argument }}
|
||||
@@ -83,7 +146,9 @@ It supports multiline text inside the block. You can have something like:
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
You can also use other expressions inside its block, such as [parameter substitution](#parameter-substitution):
|
||||
**Inner expressions:**
|
||||
|
||||
You can also embed other expressions inside its block, such as [parameter substitution](#parameter-substitution):
|
||||
|
||||
```go
|
||||
{{ with $condition }}
|
||||
@@ -91,32 +156,44 @@ You can also use other expressions inside its block, such as [parameter substitu
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
💡 Declare parameters used for `with` condition as optional. Set `optional: true` for the argument if you use it like `{{ with $argument }} .. {{ end }}`.
|
||||
This also includes nesting `with` statements:
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
function: FunctionThatOutputsConditionally
|
||||
parameters:
|
||||
- name: 'argument'
|
||||
optional: true
|
||||
code: |-
|
||||
{{ with $argument }}
|
||||
Value is: {{ . }}
|
||||
```go
|
||||
{{ with $condition1 }}
|
||||
Value of $condition1: {{ . }}
|
||||
{{ with $condition2 }}
|
||||
Value of $condition2: {{ . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
### Pipes
|
||||
|
||||
- Pipes are functions available for handling text.
|
||||
- Allows stacking actions one after another also known as "chaining".
|
||||
- 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.
|
||||
- You cannot create pipes. [A dedicated compiler](./application.md#parsing-and-compiling) provides pre-defined pipes to consume in collection files.
|
||||
- You can combine pipes with other expressions such as [parameter substitution](#parameter-substitution) and [with](#with) syntax.
|
||||
- ❗ 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, allows you to use them inside double quotes (`"`).
|
||||
- **Example usages**
|
||||
- `{{ with $code }} echo "{{ . | inlinePowerShell }}" {{ end }}`
|
||||
- `{{ with $code }} echo "{{ . | inlinePowerShell | escapeDoubleQuotes }}" {{ end }}`
|
||||
Pipes are functions designed for text manipulation.
|
||||
They allow for a sequential application of operations resembling [Unix pipelines](https://en.wikipedia.org/wiki/Pipeline_(Unix)), also known as "chaining".
|
||||
Each pipeline's output becomes the input of the following pipe.
|
||||
|
||||
**Pre-defined**:
|
||||
|
||||
Pipes are pre-defined by the system.
|
||||
You cannot create pipes in [collection files](./collection-files.md).
|
||||
[A dedicated compiler](./application.md#parsing-and-compiling) provides pre-defined pipes to consume in collection files.
|
||||
|
||||
**Compatibility:**
|
||||
|
||||
You can combine pipes with other expressions such as [parameter substitution](#parameter-substitution) and [with](#with) syntax.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
{{ with $script }} echo "{{ . | inlinePowerShell | escapeDoubleQuotes }}" {{ end }}
|
||||
```
|
||||
|
||||
**Naming:**
|
||||
|
||||
❗ Pipe names must be camelCase without any space or special characters.
|
||||
|
||||
**Available pipes:**
|
||||
|
||||
- `inlinePowerShell`: Converts a multi-lined PowerShell script to a single line.
|
||||
- `escapeDoubleQuotes`: Escapes `"` characters for batch command execution, allows you to use them inside double quotes (`"`).
|
||||
|
||||
@@ -56,6 +56,11 @@ These checks validate various qualities like runtime execution, building process
|
||||
- Use [various tools](./../package.json) and [scripts](./../scripts).
|
||||
- Are automatically executed as [GitHub workflows](./../.github/workflows).
|
||||
|
||||
### Security checks
|
||||
|
||||
- [`checks.security.sast`](./../.github/workflows/checks.security.sast.yaml): Utilizes CodeQL to conduct Static Analysis Security Testing (SAST) to ensure the secure integrity of the codebase.
|
||||
- [`checks.security.dependencies`](./../.github/workflows/checks.security.dependencies.yaml): Performs audits on third-party dependencies to identify and mitigate potential vulnerabilities, safeguarding the project from exploitable weaknesses.
|
||||
|
||||
## Tests structure
|
||||
|
||||
- [`package.json`](./../package.json): Defines test commands and includes tools used in tests.
|
||||
|
||||
@@ -8,15 +8,21 @@ import distDirs from './dist-dirs.json' assert { type: 'json' };
|
||||
const MAIN_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/main/index.ts');
|
||||
const PRELOAD_ENTRY_FILE = resolvePathFromProjectRoot('src/presentation/electron/preload/index.ts');
|
||||
const WEB_INDEX_HTML_PATH = resolvePathFromProjectRoot('src/presentation/index.html');
|
||||
const DIST_DIR = resolvePathFromProjectRoot(distDirs.electronUnbundled);
|
||||
const ELECTRON_DIST_SUBDIRECTORIES = {
|
||||
main: resolveElectronDistSubdirectory('main'),
|
||||
preload: resolveElectronDistSubdirectory('preload'),
|
||||
renderer: resolveElectronDistSubdirectory('renderer'),
|
||||
};
|
||||
|
||||
process.env.ELECTRON_ENTRY = resolve(ELECTRON_DIST_SUBDIRECTORIES.main, 'index.cjs');
|
||||
|
||||
export default defineConfig({
|
||||
main: getSharedElectronConfig({
|
||||
distDirSubfolder: 'main',
|
||||
distDirSubfolder: ELECTRON_DIST_SUBDIRECTORIES.main,
|
||||
entryFilePath: MAIN_ENTRY_FILE,
|
||||
}),
|
||||
preload: getSharedElectronConfig({
|
||||
distDirSubfolder: 'preload',
|
||||
distDirSubfolder: ELECTRON_DIST_SUBDIRECTORIES.preload,
|
||||
entryFilePath: PRELOAD_ENTRY_FILE,
|
||||
}),
|
||||
renderer: mergeConfig(
|
||||
@@ -25,7 +31,7 @@ export default defineConfig({
|
||||
}),
|
||||
{
|
||||
build: {
|
||||
outDir: resolve(DIST_DIR, 'renderer'),
|
||||
outDir: ELECTRON_DIST_SUBDIRECTORIES.renderer,
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: WEB_INDEX_HTML_PATH,
|
||||
@@ -42,7 +48,7 @@ function getSharedElectronConfig(options: {
|
||||
}): UserConfig {
|
||||
return {
|
||||
build: {
|
||||
outDir: resolve(DIST_DIR, options.distDirSubfolder),
|
||||
outDir: options.distDirSubfolder,
|
||||
lib: {
|
||||
entry: options.entryFilePath,
|
||||
},
|
||||
@@ -64,6 +70,11 @@ function getSharedElectronConfig(options: {
|
||||
};
|
||||
}
|
||||
|
||||
function resolvePathFromProjectRoot(pathSegment: string) {
|
||||
function resolvePathFromProjectRoot(pathSegment: string): string {
|
||||
return resolve(__dirname, pathSegment);
|
||||
}
|
||||
|
||||
function resolveElectronDistSubdirectory(subDirectory: string): string {
|
||||
const electronDistDir = resolvePathFromProjectRoot(distDirs.electronUnbundled);
|
||||
return resolve(electronDistDir, subDirectory);
|
||||
}
|
||||
|
||||
10680
package-lock.json
generated
90
package.json
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"name": "privacy.sexy",
|
||||
"version": "0.12.2",
|
||||
"version": "0.12.6",
|
||||
"private": true,
|
||||
"slogan": "Now you have the choice",
|
||||
"description": "Enforce privacy & security best-practices on Windows, macOS and Linux, because privacy is sexy 🍑🍆",
|
||||
"author": "undergroundwires",
|
||||
"type": "module",
|
||||
"main": "./dist-electron-unbundled/main/index.cjs",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
@@ -34,78 +33,75 @@
|
||||
"postuninstall": "electron-builder install-app-deps"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.4.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||
"@fortawesome/vue-fontawesome": "^2.0.9",
|
||||
"@floating-ui/vue": "^1.0.2",
|
||||
"@juggle/resize-observer": "^3.4.0",
|
||||
"ace-builds": "^1.23.4",
|
||||
"ace-builds": "^1.30.0",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-progressbar": "^2.1.0",
|
||||
"electron-updater": "^6.1.4",
|
||||
"file-saver": "^2.0.5",
|
||||
"markdown-it": "^13.0.1",
|
||||
"npm": "^9.8.1",
|
||||
"v-tooltip": "2.1.3",
|
||||
"vue": "^2.7.14"
|
||||
"markdown-it": "^13.0.2",
|
||||
"vue": "^3.3.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modyfi/vite-plugin-yaml": "^1.0.4",
|
||||
"@rushstack/eslint-patch": "^1.3.2",
|
||||
"@types/ace": "^0.0.48",
|
||||
"@rushstack/eslint-patch": "^1.5.1",
|
||||
"@types/ace": "^0.0.49",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@vitejs/plugin-legacy": "^4.1.1",
|
||||
"@vitejs/plugin-vue2": "^2.2.0",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"@vue/eslint-config-airbnb-with-typescript": "^7.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/test-utils": "^1.3.6",
|
||||
"autoprefixer": "^10.4.15",
|
||||
"cypress": "^12.17.2",
|
||||
"electron": "^25.3.2",
|
||||
"electron-builder": "^24.6.3",
|
||||
"@vue/test-utils": "^2.4.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"cypress": "^13.3.1",
|
||||
"electron": "^27.0.0",
|
||||
"electron-builder": "^24.6.4",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-icon-builder": "^2.0.1",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-updater": "^6.1.4",
|
||||
"electron-vite": "^1.0.27",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-plugin-cypress": "^2.14.0",
|
||||
"eslint-plugin-vue": "^9.6.0",
|
||||
"eslint-plugin-vuejs-accessibility": "^1.2.0",
|
||||
"icon-gen": "^3.0.1",
|
||||
"electron-vite": "^1.0.28",
|
||||
"eslint": "^8.51.0",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"eslint-plugin-vuejs-accessibility": "^2.2.0",
|
||||
"icon-gen": "^4.0.0",
|
||||
"jsdom": "^22.1.0",
|
||||
"markdownlint-cli": "^0.35.0",
|
||||
"postcss": "^8.4.28",
|
||||
"remark-cli": "^11.0.0",
|
||||
"markdownlint-cli": "^0.37.0",
|
||||
"postcss": "^8.4.31",
|
||||
"remark-cli": "^12.0.0",
|
||||
"remark-lint-no-dead-urls": "^1.1.0",
|
||||
"remark-preset-lint-consistent": "^5.1.2",
|
||||
"remark-validate-links": "^12.1.1",
|
||||
"sass": "^1.64.1",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"remark-validate-links": "^13.0.0",
|
||||
"sass": "^1.69.3",
|
||||
"start-server-and-test": "^2.0.1",
|
||||
"svgexport": "^0.4.2",
|
||||
"terser": "^5.19.2",
|
||||
"tslib": "~2.4.0",
|
||||
"typescript": "~4.6.2",
|
||||
"vite": "^4.4.9",
|
||||
"vitest": "^0.34.2",
|
||||
"vue-tsc": "^1.8.8",
|
||||
"terser": "^5.21.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.4.11",
|
||||
"vitest": "^0.34.6",
|
||||
"vue-tsc": "^1.8.19",
|
||||
"yaml-lint": "^1.7.0"
|
||||
},
|
||||
"//devDependencies": {
|
||||
"terser": "Used by @vitejs/plugin-legacy for minification",
|
||||
"typescript": [
|
||||
"Cannot upgrade to 5.X.X due to unmaintained @vue/cli-plugin-typescript, https://github.com/vuejs/vue-cli/issues/7401",
|
||||
"Cannot upgrade to > 4.6.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252"
|
||||
],
|
||||
"tslib": "Cannot upgrade to > 2.4.X otherwise unit tests do not work, https://github.com/evanw/node-source-map-support/issues/252",
|
||||
"@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-typescript, https://github.com/vuejs/eslint-config-typescript/pull/60",
|
||||
"@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-typescript, https://github.com/vuejs/eslint-config-typescript/pull/60"
|
||||
"@rushstack/eslint-patch": "Needed by @vue/eslint-config-typescript",
|
||||
"@vue/eslint-config-typescript": "Cannot upgrade to 12.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
|
||||
"@typescript-eslint/eslint-plugin": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58",
|
||||
"@typescript-eslint/parser": "Cannot upgrade to 6.X.X due to @vue/eslint-config-airbnb-with-typescript, https://github.com/vuejs/eslint-config-airbnb/issues/58"
|
||||
},
|
||||
"homepage": "https://privacy.sexy",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/undergroundwires/privacy.sexy.git"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
},
|
||||
"//optionalDependencies": {
|
||||
"dmg-license": "Required by `electron-builder` for DMG builds on macOS, https://github.com/electron-userland/electron-builder/issues/6489, https://github.com/electron-userland/electron-builder/issues/6520"
|
||||
}
|
||||
}
|
||||
|
||||
62
scripts/verify-web-server-status.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Description:
|
||||
* This script checks if a server, provided as a CLI argument, is up
|
||||
* and returns an HTTP 200 status code.
|
||||
* It is designed to provide easy verification of server availability
|
||||
* and will retry a specified number of times.
|
||||
*
|
||||
* Usage:
|
||||
* node ./scripts/verify-web-server-status.js --url [URL]
|
||||
*
|
||||
* Options:
|
||||
* --url URL of the server to check
|
||||
*/
|
||||
|
||||
import { get } from 'http';
|
||||
|
||||
const MAX_RETRIES = 30;
|
||||
const RETRY_DELAY_IN_SECONDS = 3;
|
||||
const URL_PARAMETER_NAME = '--url';
|
||||
|
||||
function checkServer(currentRetryCount = 1) {
|
||||
const serverUrl = getServerUrl();
|
||||
console.log(`Requesting ${serverUrl}...`);
|
||||
get(serverUrl, (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
console.log('🎊 Success: The server is up and returned HTTP 200.');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log(`Server returned HTTP status code ${res.statusCode}.`);
|
||||
retry(currentRetryCount);
|
||||
}
|
||||
}).on('error', (err) => {
|
||||
console.error('Error making the request:', err);
|
||||
retry(currentRetryCount);
|
||||
});
|
||||
}
|
||||
|
||||
function retry(currentRetryCount) {
|
||||
console.log(`Attempt ${currentRetryCount}/${MAX_RETRIES}:`);
|
||||
console.log(`Retrying in ${RETRY_DELAY_IN_SECONDS} seconds.`);
|
||||
|
||||
const remainingTime = (MAX_RETRIES - currentRetryCount) * RETRY_DELAY_IN_SECONDS;
|
||||
console.log(`Time remaining before timeout: ${remainingTime}s`);
|
||||
|
||||
if (currentRetryCount < MAX_RETRIES) {
|
||||
setTimeout(() => checkServer(currentRetryCount + 1), RETRY_DELAY_IN_SECONDS * 1000);
|
||||
} else {
|
||||
console.log('Failure: The server at did not return HTTP 200 within the allocated time. Exiting.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getServerUrl() {
|
||||
const urlIndex = process.argv.indexOf(URL_PARAMETER_NAME);
|
||||
if (urlIndex === -1 || urlIndex === process.argv.length - 1) {
|
||||
console.error(`Parameter "${URL_PARAMETER_NAME}" is not provided.`);
|
||||
process.exit(1);
|
||||
}
|
||||
return process.argv[urlIndex + 1];
|
||||
}
|
||||
|
||||
checkServer();
|
||||
@@ -14,45 +14,44 @@ export class ExpressionRegexBuilder {
|
||||
.addRawRegex('\\s+');
|
||||
}
|
||||
|
||||
public matchPipeline() {
|
||||
public captureOptionalPipeline() {
|
||||
return this
|
||||
.expectZeroOrMoreWhitespaces()
|
||||
.addRawRegex('(\\|\\s*.+?)?');
|
||||
.addRawRegex('((?:\\|\\s*\\b[a-zA-Z]+\\b\\s*)*)');
|
||||
}
|
||||
|
||||
public matchUntilFirstWhitespace() {
|
||||
public captureUntilWhitespaceOrPipe() {
|
||||
return this
|
||||
.addRawRegex('([^|\\s]+)');
|
||||
}
|
||||
|
||||
public matchMultilineAnythingExceptSurroundingWhitespaces() {
|
||||
public captureMultilineAnythingExceptSurroundingWhitespaces() {
|
||||
return this
|
||||
.expectZeroOrMoreWhitespaces()
|
||||
.addRawRegex('([\\S\\s]+?)')
|
||||
.expectZeroOrMoreWhitespaces();
|
||||
.expectOptionalWhitespaces()
|
||||
.addRawRegex('([\\s\\S]*\\S)')
|
||||
.expectOptionalWhitespaces();
|
||||
}
|
||||
|
||||
public expectExpressionStart() {
|
||||
return this
|
||||
.expectCharacters('{{')
|
||||
.expectZeroOrMoreWhitespaces();
|
||||
.expectOptionalWhitespaces();
|
||||
}
|
||||
|
||||
public expectExpressionEnd() {
|
||||
return this
|
||||
.expectZeroOrMoreWhitespaces()
|
||||
.expectOptionalWhitespaces()
|
||||
.expectCharacters('}}');
|
||||
}
|
||||
|
||||
public expectOptionalWhitespaces() {
|
||||
return this
|
||||
.addRawRegex('\\s*');
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -6,8 +6,9 @@ export class ParameterSubstitutionParser extends RegexParser {
|
||||
protected readonly regex = new ExpressionRegexBuilder()
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('$')
|
||||
.matchUntilFirstWhitespace() // First match: Parameter name
|
||||
.matchPipeline() // Second match: Pipeline
|
||||
.captureUntilWhitespaceOrPipe() // First capture: Parameter name
|
||||
.expectOptionalWhitespaces()
|
||||
.captureOptionalPipeline() // Second capture: Pipeline
|
||||
.expectExpressionEnd()
|
||||
.buildRegExp();
|
||||
|
||||
|
||||
@@ -1,59 +1,222 @@
|
||||
// eslint-disable-next-line max-classes-per-file
|
||||
import { IExpressionParser } from '@/application/Parser/Script/Compiler/Expressions/Parser/IExpressionParser';
|
||||
import { FunctionParameterCollection } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameterCollection';
|
||||
import { FunctionParameter } from '@/application/Parser/Script/Compiler/Function/Parameter/FunctionParameter';
|
||||
import { RegexParser, IPrimitiveExpression } from '../Parser/Regex/RegexParser';
|
||||
import { IExpression } from '../Expression/IExpression';
|
||||
import { ExpressionPosition } from '../Expression/ExpressionPosition';
|
||||
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()
|
||||
// ...
|
||||
.matchMultilineAnythingExceptSurroundingWhitespaces() // Second match: Scope text
|
||||
// {{ end }}
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('end')
|
||||
.expectExpressionEnd()
|
||||
.buildRegExp();
|
||||
export class WithParser implements IExpressionParser {
|
||||
public findExpressions(code: string): IExpression[] {
|
||||
if (!code) {
|
||||
throw new Error('missing code');
|
||||
}
|
||||
return parseWithExpressions(code);
|
||||
}
|
||||
}
|
||||
|
||||
protected buildExpression(match: RegExpMatchArray): IPrimitiveExpression {
|
||||
const parameterName = match[1];
|
||||
const scopeText = match[2];
|
||||
enum WithStatementType {
|
||||
Start,
|
||||
End,
|
||||
ContextVariable,
|
||||
}
|
||||
|
||||
type WithStatement = {
|
||||
readonly type: WithStatementType.Start;
|
||||
readonly parameterName: string;
|
||||
readonly position: ExpressionPosition;
|
||||
} | {
|
||||
readonly type: WithStatementType.End;
|
||||
readonly position: ExpressionPosition;
|
||||
} | {
|
||||
readonly type: WithStatementType.ContextVariable;
|
||||
readonly position: ExpressionPosition;
|
||||
readonly pipeline: string | undefined;
|
||||
};
|
||||
|
||||
function parseAllWithExpressions(
|
||||
input: string,
|
||||
): WithStatement[] {
|
||||
const expressions = new Array<WithStatement>();
|
||||
for (const match of input.matchAll(WithStatementStartRegEx)) {
|
||||
expressions.push({
|
||||
type: WithStatementType.Start,
|
||||
parameterName: match[1],
|
||||
position: createPosition(match),
|
||||
});
|
||||
}
|
||||
for (const match of input.matchAll(WithStatementEndRegEx)) {
|
||||
expressions.push({
|
||||
type: WithStatementType.End,
|
||||
position: createPosition(match),
|
||||
});
|
||||
}
|
||||
for (const match of input.matchAll(ContextVariableWithPipelineRegEx)) {
|
||||
expressions.push({
|
||||
type: WithStatementType.ContextVariable,
|
||||
position: createPosition(match),
|
||||
pipeline: match[1],
|
||||
});
|
||||
}
|
||||
return expressions;
|
||||
}
|
||||
|
||||
function createPosition(match: RegExpMatchArray): ExpressionPosition {
|
||||
const startPos = match.index;
|
||||
const endPos = startPos + match[0].length;
|
||||
return new ExpressionPosition(startPos, endPos);
|
||||
}
|
||||
|
||||
class WithStatementBuilder {
|
||||
private readonly contextVariables = new Array<{
|
||||
readonly positionInScope: ExpressionPosition;
|
||||
readonly pipeline: string | undefined;
|
||||
}>();
|
||||
|
||||
public addContextVariable(
|
||||
absolutePosition: ExpressionPosition,
|
||||
pipeline: string | undefined,
|
||||
): void {
|
||||
const positionInScope = new ExpressionPosition(
|
||||
absolutePosition.start - this.startExpressionPosition.end,
|
||||
absolutePosition.end - this.startExpressionPosition.end,
|
||||
);
|
||||
this.contextVariables.push({
|
||||
positionInScope,
|
||||
pipeline,
|
||||
});
|
||||
}
|
||||
|
||||
public buildExpression(endExpressionPosition: ExpressionPosition, input: string): IExpression {
|
||||
const parameters = new FunctionParameterCollection();
|
||||
parameters.addParameter(new FunctionParameter(this.parameterName, true));
|
||||
const position = new ExpressionPosition(
|
||||
this.startExpressionPosition.start,
|
||||
endExpressionPosition.end,
|
||||
);
|
||||
const scope = input.substring(this.startExpressionPosition.end, endExpressionPosition.start);
|
||||
return {
|
||||
parameters: [new FunctionParameter(parameterName, true)],
|
||||
evaluator: (context) => {
|
||||
const argumentValue = context.args.hasArgument(parameterName)
|
||||
? context.args.getArgument(parameterName).argumentValue
|
||||
parameters,
|
||||
position,
|
||||
evaluate: (context) => {
|
||||
const argumentValue = context.args.hasArgument(this.parameterName)
|
||||
? context.args.getArgument(this.parameterName).argumentValue
|
||||
: undefined;
|
||||
if (!argumentValue) {
|
||||
return '';
|
||||
}
|
||||
return replaceEachScopeSubstitution(scopeText, (pipeline) => {
|
||||
const substitutedScope = this.substituteContextVariables(scope, (pipeline) => {
|
||||
if (!pipeline) {
|
||||
return argumentValue;
|
||||
}
|
||||
return context.pipelineCompiler.compile(argumentValue, pipeline);
|
||||
});
|
||||
return substitutedScope;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly startExpressionPosition: ExpressionPosition,
|
||||
private readonly parameterName: string,
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
private substituteContextVariables(
|
||||
scope: string,
|
||||
substituter: (pipeline: string) => string,
|
||||
): string {
|
||||
if (!this.contextVariables.length) {
|
||||
return scope;
|
||||
}
|
||||
let substitutedScope = '';
|
||||
let scopeSubstrIndex = 0;
|
||||
for (const contextVariable of this.contextVariables) {
|
||||
substitutedScope += scope.substring(scopeSubstrIndex, contextVariable.positionInScope.start);
|
||||
substitutedScope += substituter(contextVariable.pipeline);
|
||||
scopeSubstrIndex = contextVariable.positionInScope.end;
|
||||
}
|
||||
substitutedScope += scope.substring(scopeSubstrIndex, scope.length);
|
||||
return substitutedScope;
|
||||
}
|
||||
}
|
||||
|
||||
const ScopeSubstitutionRegEx = new ExpressionRegexBuilder()
|
||||
function buildErrorContext(code: string, statements: readonly WithStatement[]): string {
|
||||
const formattedStatements = statements.map((s) => `- [${s.position.start}, ${s.position.end}] ${WithStatementType[s.type]}`).join('\n');
|
||||
return [
|
||||
'Code:', '---', code, '---',
|
||||
'nStatements:', '---', formattedStatements, '---',
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function parseWithExpressions(input: string): IExpression[] {
|
||||
const allStatements = parseAllWithExpressions(input);
|
||||
const sortedStatements = allStatements
|
||||
.slice()
|
||||
.sort((a, b) => b.position.start - a.position.start);
|
||||
const expressions = new Array<IExpression>();
|
||||
const builders = new Array<WithStatementBuilder>();
|
||||
const throwWithContext = (message: string) => {
|
||||
throw new Error(`${message}\n${buildErrorContext(input, allStatements)}}`);
|
||||
};
|
||||
while (sortedStatements.length > 0) {
|
||||
const statement = sortedStatements.pop();
|
||||
if (!statement) {
|
||||
break;
|
||||
}
|
||||
switch (statement.type) { // eslint-disable-line default-case
|
||||
case WithStatementType.Start:
|
||||
builders.push(new WithStatementBuilder(
|
||||
statement.position,
|
||||
statement.parameterName,
|
||||
));
|
||||
break;
|
||||
case WithStatementType.ContextVariable:
|
||||
if (builders.length === 0) {
|
||||
throwWithContext('Context variable before `with` statement.');
|
||||
}
|
||||
builders[builders.length - 1].addContextVariable(statement.position, statement.pipeline);
|
||||
break;
|
||||
case WithStatementType.End:
|
||||
if (builders.length === 0) {
|
||||
throwWithContext('Redundant `end` statement, missing `with`?');
|
||||
}
|
||||
expressions.push(builders.pop().buildExpression(statement.position, input));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (builders.length > 0) {
|
||||
throwWithContext('Missing `end` statement, forgot `{{ end }}?');
|
||||
}
|
||||
return expressions;
|
||||
}
|
||||
|
||||
const ContextVariableWithPipelineRegEx = new ExpressionRegexBuilder()
|
||||
// {{ . | pipeName }}
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('.')
|
||||
.matchPipeline() // First match: pipeline
|
||||
.expectOptionalWhitespaces()
|
||||
.captureOptionalPipeline() // First capture: pipeline
|
||||
.expectExpressionEnd()
|
||||
.buildRegExp();
|
||||
|
||||
function replaceEachScopeSubstitution(scopeText: string, replacer: (pipeline: string) => string) {
|
||||
// Not using /{{\s*.\s*(?:(\|\s*[^{}]*?)\s*)?}}/g for not matching brackets,
|
||||
// but instead letting the pipeline compiler to fail on those.
|
||||
return scopeText.replaceAll(ScopeSubstitutionRegEx, (_$, match1) => {
|
||||
return replacer(match1);
|
||||
});
|
||||
}
|
||||
const WithStatementStartRegEx = new ExpressionRegexBuilder()
|
||||
// {{ with $parameterName }}
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('with')
|
||||
.expectOneOrMoreWhitespaces()
|
||||
.expectCharacters('$')
|
||||
.captureUntilWhitespaceOrPipe() // First capture: parameter name
|
||||
.expectExpressionEnd()
|
||||
.expectOptionalWhitespaces()
|
||||
.buildRegExp();
|
||||
|
||||
const WithStatementEndRegEx = new ExpressionRegexBuilder()
|
||||
// {{ end }}
|
||||
.expectOptionalWhitespaces()
|
||||
.expectExpressionStart()
|
||||
.expectCharacters('end')
|
||||
.expectOptionalWhitespaces()
|
||||
.expectExpressionEnd()
|
||||
.buildRegExp();
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { CompiledCode } from '../CompiledCode';
|
||||
|
||||
export interface CodeSegmentMerger {
|
||||
mergeCodeParts(codeSegments: readonly CompiledCode[]): CompiledCode;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { CompiledCode } from '../CompiledCode';
|
||||
import { CodeSegmentMerger } from './CodeSegmentMerger';
|
||||
|
||||
export class NewlineCodeSegmentMerger implements CodeSegmentMerger {
|
||||
public mergeCodeParts(codeSegments: readonly CompiledCode[]): CompiledCode {
|
||||
if (!codeSegments?.length) {
|
||||
throw new Error('missing segments');
|
||||
}
|
||||
return {
|
||||
code: joinCodeParts(codeSegments.map((f) => f.code)),
|
||||
revertCode: joinCodeParts(codeSegments.map((f) => f.revertCode)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function joinCodeParts(codeSegments: readonly string[]): string {
|
||||
return codeSegments
|
||||
.filter((segment) => segment?.length > 0)
|
||||
.join('\n');
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface ICompiledCode {
|
||||
export interface CompiledCode {
|
||||
readonly code: string;
|
||||
readonly revertCode?: string;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||
import { FunctionCall } from '../FunctionCall';
|
||||
import type { SingleCallCompiler } from './SingleCall/SingleCallCompiler';
|
||||
|
||||
export interface FunctionCallCompilationContext {
|
||||
readonly allFunctions: ISharedFunctionCollection;
|
||||
readonly rootCallSequence: readonly FunctionCall[];
|
||||
readonly singleCallCompiler: SingleCallCompiler;
|
||||
}
|
||||
@@ -1,149 +1,10 @@
|
||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||
import { IFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/IFunctionCall';
|
||||
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
||||
import { IExpressionsCompiler } from '../../../Expressions/IExpressionsCompiler';
|
||||
import { ExpressionsCompiler } from '../../../Expressions/ExpressionsCompiler';
|
||||
import { ISharedFunction, IFunctionCode } from '../../ISharedFunction';
|
||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||
import { FunctionCall } from '../FunctionCall';
|
||||
import { FunctionCallArgumentCollection } from '../Argument/FunctionCallArgumentCollection';
|
||||
import { IFunctionCallCompiler } from './IFunctionCallCompiler';
|
||||
import { ICompiledCode } from './ICompiledCode';
|
||||
import { CompiledCode } from './CompiledCode';
|
||||
|
||||
export class FunctionCallCompiler implements IFunctionCallCompiler {
|
||||
public static readonly instance: IFunctionCallCompiler = new FunctionCallCompiler();
|
||||
|
||||
protected constructor(
|
||||
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
||||
) {
|
||||
}
|
||||
|
||||
public compileCall(
|
||||
calls: IFunctionCall[],
|
||||
export interface FunctionCallCompiler {
|
||||
compileFunctionCalls(
|
||||
calls: readonly FunctionCall[],
|
||||
functions: ISharedFunctionCollection,
|
||||
): ICompiledCode {
|
||||
if (!functions) { throw new Error('missing functions'); }
|
||||
if (!calls) { throw new Error('missing calls'); }
|
||||
if (calls.some((f) => !f)) { throw new Error('missing 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 = context.callSequence
|
||||
.flatMap((call) => compileSingleCall(call, context));
|
||||
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];
|
||||
}
|
||||
// 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.execute, args),
|
||||
revertCode: compiler.compileExpressions(code.revert, args),
|
||||
};
|
||||
}
|
||||
|
||||
function compileArgs(
|
||||
argsToCompile: IReadOnlyFunctionCallArgumentCollection,
|
||||
args: IReadOnlyFunctionCallArgumentCollection,
|
||||
compiler: IExpressionsCompiler,
|
||||
): IReadOnlyFunctionCallArgumentCollection {
|
||||
return argsToCompile
|
||||
.getAllParameterNames()
|
||||
.map((parameterName) => {
|
||||
const { argumentValue } = argsToCompile.getArgument(parameterName);
|
||||
const compiledValue = compiler.compileExpressions(argumentValue, args);
|
||||
return new FunctionCallArgument(parameterName, compiledValue);
|
||||
})
|
||||
.reduce((compiledArgs, arg) => {
|
||||
compiledArgs.addArgument(arg);
|
||||
return compiledArgs;
|
||||
}, new FunctionCallArgumentCollection());
|
||||
}
|
||||
|
||||
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(
|
||||
// eslint-disable-next-line prefer-template
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: `
|
||||
+ `"${unexpectedParameters.join('", "')}"`
|
||||
+ '. Expected parameter(s): '
|
||||
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
|
||||
);
|
||||
): CompiledCode;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { ISharedFunctionCollection } from '../../ISharedFunctionCollection';
|
||||
import { FunctionCallCompiler } from './FunctionCallCompiler';
|
||||
import { CompiledCode } from './CompiledCode';
|
||||
import { FunctionCallCompilationContext } from './FunctionCallCompilationContext';
|
||||
import { SingleCallCompiler } from './SingleCall/SingleCallCompiler';
|
||||
import { AdaptiveFunctionCallCompiler } from './SingleCall/AdaptiveFunctionCallCompiler';
|
||||
import { CodeSegmentMerger } from './CodeSegmentJoin/CodeSegmentMerger';
|
||||
import { NewlineCodeSegmentMerger } from './CodeSegmentJoin/NewlineCodeSegmentMerger';
|
||||
|
||||
export class FunctionCallSequenceCompiler implements FunctionCallCompiler {
|
||||
public static readonly instance: FunctionCallCompiler = new FunctionCallSequenceCompiler();
|
||||
|
||||
/* The constructor is protected to enforce the singleton pattern. */
|
||||
protected constructor(
|
||||
private readonly singleCallCompiler: SingleCallCompiler = new AdaptiveFunctionCallCompiler(),
|
||||
private readonly codeSegmentMerger: CodeSegmentMerger = new NewlineCodeSegmentMerger(),
|
||||
) { }
|
||||
|
||||
public compileFunctionCalls(
|
||||
calls: readonly FunctionCall[],
|
||||
functions: ISharedFunctionCollection,
|
||||
): CompiledCode {
|
||||
if (!functions) { throw new Error('missing functions'); }
|
||||
if (!calls?.length) { throw new Error('missing calls'); }
|
||||
if (calls.some((f) => !f)) { throw new Error('missing function call'); }
|
||||
const context: FunctionCallCompilationContext = {
|
||||
allFunctions: functions,
|
||||
rootCallSequence: calls,
|
||||
singleCallCompiler: this.singleCallCompiler,
|
||||
};
|
||||
const codeSegments = context.rootCallSequence
|
||||
.flatMap((call) => this.singleCallCompiler.compileSingleCall(call, context));
|
||||
return this.codeSegmentMerger.mergeCodeParts(codeSegments);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { ISharedFunctionCollection } from '@/application/Parser/Script/Compiler/Function/ISharedFunctionCollection';
|
||||
import { IFunctionCall } from '../IFunctionCall';
|
||||
import { ICompiledCode } from './ICompiledCode';
|
||||
|
||||
export interface IFunctionCallCompiler {
|
||||
compileCall(
|
||||
calls: IFunctionCall[],
|
||||
functions: ISharedFunctionCollection): ICompiledCode;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { FunctionCall } from '../../FunctionCall';
|
||||
import { CompiledCode } from '../CompiledCode';
|
||||
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
||||
import { IReadOnlyFunctionCallArgumentCollection } from '../../Argument/IFunctionCallArgumentCollection';
|
||||
import { ISharedFunction } from '../../../ISharedFunction';
|
||||
import { SingleCallCompiler } from './SingleCallCompiler';
|
||||
import { SingleCallCompilerStrategy } from './SingleCallCompilerStrategy';
|
||||
import { InlineFunctionCallCompiler } from './Strategies/InlineFunctionCallCompiler';
|
||||
import { NestedFunctionCallCompiler } from './Strategies/NestedFunctionCallCompiler';
|
||||
|
||||
export class AdaptiveFunctionCallCompiler implements SingleCallCompiler {
|
||||
public constructor(
|
||||
private readonly strategies: SingleCallCompilerStrategy[] = [
|
||||
new InlineFunctionCallCompiler(),
|
||||
new NestedFunctionCallCompiler(),
|
||||
],
|
||||
) {
|
||||
}
|
||||
|
||||
public compileSingleCall(
|
||||
call: FunctionCall,
|
||||
context: FunctionCallCompilationContext,
|
||||
): CompiledCode[] {
|
||||
const func = context.allFunctions.getFunctionByName(call.functionName);
|
||||
ensureThatCallArgumentsExistInParameterDefinition(func, call.args);
|
||||
const strategy = this.findStrategy(func);
|
||||
return strategy.compileFunction(func, call, context);
|
||||
}
|
||||
|
||||
private findStrategy(func: ISharedFunction): SingleCallCompilerStrategy {
|
||||
const strategies = this.strategies.filter((strategy) => strategy.canCompile(func));
|
||||
if (strategies.length > 1) {
|
||||
throw new Error('Multiple strategies found to compile the function call.');
|
||||
}
|
||||
if (strategies.length === 0) {
|
||||
throw new Error('No strategies found to compile the function call.');
|
||||
}
|
||||
return strategies[0];
|
||||
}
|
||||
}
|
||||
|
||||
function ensureThatCallArgumentsExistInParameterDefinition(
|
||||
func: ISharedFunction,
|
||||
callArguments: IReadOnlyFunctionCallArgumentCollection,
|
||||
): void {
|
||||
const callArgumentNames = callArguments.getAllParameterNames();
|
||||
const functionParameterNames = func.parameters.all.map((param) => param.name) || [];
|
||||
const unexpectedParameters = findUnexpectedParameters(callArgumentNames, functionParameterNames);
|
||||
throwIfUnexpectedParametersExist(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 throwIfUnexpectedParametersExist(
|
||||
functionName: string,
|
||||
unexpectedParameters: string[],
|
||||
expectedParameters: string[],
|
||||
) {
|
||||
if (!unexpectedParameters.length) {
|
||||
return;
|
||||
}
|
||||
throw new Error(
|
||||
// eslint-disable-next-line prefer-template
|
||||
`Function "${functionName}" has unexpected parameter(s) provided: `
|
||||
+ `"${unexpectedParameters.join('", "')}"`
|
||||
+ '. Expected parameter(s): '
|
||||
+ (expectedParameters.length ? `"${expectedParameters.join('", "')}"` : 'none'),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { FunctionCall } from '../../FunctionCall';
|
||||
import { CompiledCode } from '../CompiledCode';
|
||||
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
||||
|
||||
export interface SingleCallCompiler {
|
||||
compileSingleCall(
|
||||
call: FunctionCall,
|
||||
context: FunctionCallCompilationContext,
|
||||
): CompiledCode[];
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { CompiledCode } from '../CompiledCode';
|
||||
import { FunctionCallCompilationContext } from '../FunctionCallCompilationContext';
|
||||
|
||||
export interface SingleCallCompilerStrategy {
|
||||
canCompile(func: ISharedFunction): boolean;
|
||||
compileFunction(
|
||||
calledFunction: ISharedFunction,
|
||||
callToFunction: FunctionCall,
|
||||
context: FunctionCallCompilationContext,
|
||||
): CompiledCode[],
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||
|
||||
export interface ArgumentCompiler {
|
||||
createCompiledNestedCall(
|
||||
nestedFunctionCall: FunctionCall,
|
||||
parentFunctionCall: FunctionCall,
|
||||
context: FunctionCallCompilationContext,
|
||||
): FunctionCall;
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { IReadOnlyFunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/IFunctionCallArgumentCollection';
|
||||
import { FunctionCallArgument } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgument';
|
||||
import { FunctionCallArgumentCollection } from '@/application/Parser/Script/Compiler/Function/Call/Argument/FunctionCallArgumentCollection';
|
||||
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||
import { ParsedFunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/ParsedFunctionCall';
|
||||
import { ArgumentCompiler } from './ArgumentCompiler';
|
||||
|
||||
export class NestedFunctionArgumentCompiler implements ArgumentCompiler {
|
||||
constructor(
|
||||
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
||||
) { }
|
||||
|
||||
public createCompiledNestedCall(
|
||||
nestedFunction: FunctionCall,
|
||||
parentFunction: FunctionCall,
|
||||
context: FunctionCallCompilationContext,
|
||||
): FunctionCall {
|
||||
const compiledArgs = compileNestedFunctionArguments(
|
||||
nestedFunction,
|
||||
parentFunction.args,
|
||||
context,
|
||||
this.expressionsCompiler,
|
||||
);
|
||||
const compiledCall = new ParsedFunctionCall(nestedFunction.functionName, compiledArgs);
|
||||
return compiledCall;
|
||||
}
|
||||
}
|
||||
|
||||
function compileNestedFunctionArguments(
|
||||
nestedFunction: FunctionCall,
|
||||
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
|
||||
context: FunctionCallCompilationContext,
|
||||
expressionsCompiler: IExpressionsCompiler,
|
||||
): IReadOnlyFunctionCallArgumentCollection {
|
||||
const requiredParameterNames = context
|
||||
.allFunctions
|
||||
.getRequiredParameterNames(nestedFunction.functionName);
|
||||
const compiledArguments = nestedFunction.args
|
||||
.getAllParameterNames()
|
||||
// Compile each argument value
|
||||
.map((paramName) => ({
|
||||
parameterName: paramName,
|
||||
compiledArgumentValue: compileArgument(
|
||||
paramName,
|
||||
nestedFunction,
|
||||
parentFunctionArgs,
|
||||
expressionsCompiler,
|
||||
),
|
||||
}))
|
||||
// Filter out arguments with absent values
|
||||
.filter(({
|
||||
parameterName,
|
||||
compiledArgumentValue,
|
||||
}) => isValidNonAbsentArgumentValue(
|
||||
parameterName,
|
||||
compiledArgumentValue,
|
||||
requiredParameterNames,
|
||||
))
|
||||
/*
|
||||
Create argument object with non-absent values.
|
||||
This is done after eliminating absent values because otherwise creating argument object
|
||||
with absent values throws error.
|
||||
*/
|
||||
.map(({
|
||||
parameterName,
|
||||
compiledArgumentValue,
|
||||
}) => new FunctionCallArgument(parameterName, compiledArgumentValue));
|
||||
return buildArgumentCollectionFromArguments(compiledArguments);
|
||||
}
|
||||
|
||||
function isValidNonAbsentArgumentValue(
|
||||
parameterName: string,
|
||||
argumentValue: string | undefined,
|
||||
requiredParameterNames: string[],
|
||||
): boolean {
|
||||
if (argumentValue) {
|
||||
return true;
|
||||
}
|
||||
if (!requiredParameterNames.includes(parameterName)) {
|
||||
return false;
|
||||
}
|
||||
throw new Error(`Compilation resulted in empty value for required parameter: "${parameterName}"`);
|
||||
}
|
||||
|
||||
function compileArgument(
|
||||
parameterName: string,
|
||||
nestedFunction: FunctionCall,
|
||||
parentFunctionArgs: IReadOnlyFunctionCallArgumentCollection,
|
||||
expressionsCompiler: IExpressionsCompiler,
|
||||
): string {
|
||||
try {
|
||||
const { argumentValue: codeInArgument } = nestedFunction.args.getArgument(parameterName);
|
||||
return expressionsCompiler.compileExpressions(codeInArgument, parentFunctionArgs);
|
||||
} catch (err) {
|
||||
throw new AggregateError([err], `Error when compiling argument for "${parameterName}"`);
|
||||
}
|
||||
}
|
||||
|
||||
function buildArgumentCollectionFromArguments(
|
||||
args: FunctionCallArgument[],
|
||||
): FunctionCallArgumentCollection {
|
||||
return args.reduce((compiledArgs, arg) => {
|
||||
compiledArgs.addArgument(arg);
|
||||
return compiledArgs;
|
||||
}, new FunctionCallArgumentCollection());
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { ExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/ExpressionsCompiler';
|
||||
import { IExpressionsCompiler } from '@/application/Parser/Script/Compiler/Expressions/IExpressionsCompiler';
|
||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||
import { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
|
||||
|
||||
export class InlineFunctionCallCompiler implements SingleCallCompilerStrategy {
|
||||
public constructor(
|
||||
private readonly expressionsCompiler: IExpressionsCompiler = new ExpressionsCompiler(),
|
||||
) {
|
||||
}
|
||||
|
||||
public canCompile(func: ISharedFunction): boolean {
|
||||
return func.body.code !== undefined;
|
||||
}
|
||||
|
||||
public compileFunction(
|
||||
calledFunction: ISharedFunction,
|
||||
callToFunction: FunctionCall,
|
||||
): CompiledCode[] {
|
||||
const { code } = calledFunction.body;
|
||||
const { args } = callToFunction;
|
||||
return [
|
||||
{
|
||||
code: this.expressionsCompiler.compileExpressions(code.execute, args),
|
||||
revertCode: this.expressionsCompiler.compileExpressions(code.revert, args),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ISharedFunction } from '@/application/Parser/Script/Compiler/Function/ISharedFunction';
|
||||
import { FunctionCall } from '@/application/Parser/Script/Compiler/Function/Call/FunctionCall';
|
||||
import { FunctionCallCompilationContext } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/FunctionCallCompilationContext';
|
||||
import { CompiledCode } from '@/application/Parser/Script/Compiler/Function/Call/Compiler/CompiledCode';
|
||||
import { SingleCallCompilerStrategy } from '../SingleCallCompilerStrategy';
|
||||
import { ArgumentCompiler } from './Argument/ArgumentCompiler';
|
||||
import { NestedFunctionArgumentCompiler } from './Argument/NestedFunctionArgumentCompiler';
|
||||
|
||||
export class NestedFunctionCallCompiler implements SingleCallCompilerStrategy {
|
||||
public constructor(
|
||||
private readonly argumentCompiler: ArgumentCompiler = new NestedFunctionArgumentCompiler(),
|
||||
) {
|
||||
}
|
||||
|
||||
public canCompile(func: ISharedFunction): boolean {
|
||||
return func.body.calls !== undefined;
|
||||
}
|
||||
|
||||
public compileFunction(
|
||||
calledFunction: ISharedFunction,
|
||||
callToFunction: FunctionCall,
|
||||
context: FunctionCallCompilationContext,
|
||||
): CompiledCode[] {
|
||||
const nestedCalls = calledFunction.body.calls;
|
||||
return nestedCalls.map((nestedCall) => {
|
||||
try {
|
||||
const compiledParentCall = this.argumentCompiler
|
||||
.createCompiledNestedCall(nestedCall, callToFunction, context);
|
||||
const compiledNestedCall = context.singleCallCompiler
|
||||
.compileSingleCall(compiledParentCall, context);
|
||||
return compiledNestedCall;
|
||||
} catch (err) {
|
||||
throw new AggregateError([err], `Error with call to "${nestedCall.functionName}" function from "${callToFunction.functionName}" function`);
|
||||
}
|
||||
}).flat();
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,6 @@
|
||||
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('missing function name in function call');
|
||||
}
|
||||
if (!args) {
|
||||
throw new Error('missing args');
|
||||
}
|
||||
}
|
||||
export interface FunctionCall {
|
||||
readonly functionName: string;
|
||||
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { FunctionCallData, FunctionCallsData, FunctionCallParametersData } from '@/application/collections/';
|
||||
import { IFunctionCall } from './IFunctionCall';
|
||||
import { FunctionCall } from './FunctionCall';
|
||||
import { FunctionCallArgumentCollection } from './Argument/FunctionCallArgumentCollection';
|
||||
import { FunctionCallArgument } from './Argument/FunctionCallArgument';
|
||||
import { FunctionCall } from './FunctionCall';
|
||||
import { ParsedFunctionCall } from './ParsedFunctionCall';
|
||||
|
||||
export function parseFunctionCalls(calls: FunctionCallsData): IFunctionCall[] {
|
||||
export function parseFunctionCalls(calls: FunctionCallsData): FunctionCall[] {
|
||||
if (calls === undefined) {
|
||||
throw new Error('missing call data');
|
||||
}
|
||||
@@ -22,12 +22,12 @@ function getCallSequence(calls: FunctionCallsData): FunctionCallData[] {
|
||||
return [calls as FunctionCallData];
|
||||
}
|
||||
|
||||
function parseFunctionCall(call: FunctionCallData): IFunctionCall {
|
||||
function parseFunctionCall(call: FunctionCallData): FunctionCall {
|
||||
if (!call) {
|
||||
throw new Error('missing call data');
|
||||
}
|
||||
const callArgs = parseArgs(call.parameters);
|
||||
return new FunctionCall(call.function, callArgs);
|
||||
return new ParsedFunctionCall(call.function, callArgs);
|
||||
}
|
||||
|
||||
function parseArgs(
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||
|
||||
export interface IFunctionCall {
|
||||
readonly functionName: string;
|
||||
readonly args: IReadOnlyFunctionCallArgumentCollection;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { IReadOnlyFunctionCallArgumentCollection } from './Argument/IFunctionCallArgumentCollection';
|
||||
import { FunctionCall } from './FunctionCall';
|
||||
|
||||
export class ParsedFunctionCall implements FunctionCall {
|
||||
constructor(
|
||||
public readonly functionName: string,
|
||||
public readonly args: IReadOnlyFunctionCallArgumentCollection,
|
||||
) {
|
||||
if (!functionName) {
|
||||
throw new Error('missing function name in function call');
|
||||
}
|
||||
if (!args) {
|
||||
throw new Error('missing args');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParameterCollection';
|
||||
import { IFunctionCall } from './Call/IFunctionCall';
|
||||
import { FunctionCall } from './Call/FunctionCall';
|
||||
|
||||
export interface ISharedFunction {
|
||||
readonly name: string;
|
||||
@@ -9,8 +9,8 @@ export interface ISharedFunction {
|
||||
|
||||
export interface ISharedFunctionBody {
|
||||
readonly type: FunctionBodyType;
|
||||
readonly code: IFunctionCode;
|
||||
readonly calls: readonly IFunctionCall[];
|
||||
readonly code: IFunctionCode | undefined;
|
||||
readonly calls: readonly FunctionCall[] | undefined;
|
||||
}
|
||||
|
||||
export enum FunctionBodyType {
|
||||
|
||||
@@ -2,4 +2,5 @@ import { ISharedFunction } from './ISharedFunction';
|
||||
|
||||
export interface ISharedFunctionCollection {
|
||||
getFunctionByName(name: string): ISharedFunction;
|
||||
getRequiredParameterNames(functionName: string): string[];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IFunctionCall } from './Call/IFunctionCall';
|
||||
import { FunctionCall } from './Call/FunctionCall';
|
||||
|
||||
import {
|
||||
FunctionBodyType, IFunctionCode, ISharedFunction, ISharedFunctionBody,
|
||||
@@ -8,7 +8,7 @@ import { IReadOnlyFunctionParameterCollection } from './Parameter/IFunctionParam
|
||||
export function createCallerFunction(
|
||||
name: string,
|
||||
parameters: IReadOnlyFunctionParameterCollection,
|
||||
callSequence: readonly IFunctionCall[],
|
||||
callSequence: readonly FunctionCall[],
|
||||
): ISharedFunction {
|
||||
if (!callSequence || !callSequence.length) {
|
||||
throw new Error(`missing call sequence in function "${name}"`);
|
||||
@@ -38,7 +38,7 @@ class SharedFunction implements ISharedFunction {
|
||||
constructor(
|
||||
public readonly name: string,
|
||||
public readonly parameters: IReadOnlyFunctionParameterCollection,
|
||||
content: IFunctionCode | readonly IFunctionCall[],
|
||||
content: IFunctionCode | readonly FunctionCall[],
|
||||
bodyType: FunctionBodyType,
|
||||
) {
|
||||
if (!name) { throw new Error('missing function name'); }
|
||||
@@ -46,7 +46,7 @@ class SharedFunction implements ISharedFunction {
|
||||
this.body = {
|
||||
type: bodyType,
|
||||
code: bodyType === FunctionBodyType.Code ? content as IFunctionCode : undefined,
|
||||
calls: bodyType === FunctionBodyType.Calls ? content as readonly IFunctionCall[] : undefined,
|
||||
calls: bodyType === FunctionBodyType.Calls ? content as readonly FunctionCall[] : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,15 @@ export class SharedFunctionCollection implements ISharedFunctionCollection {
|
||||
return func;
|
||||
}
|
||||
|
||||
public getRequiredParameterNames(functionName: string): string[] {
|
||||
return this
|
||||
.getFunctionByName(functionName)
|
||||
.parameters
|
||||
.all
|
||||
.filter((parameter) => !parameter.isOptional)
|
||||
.map((parameter) => parameter.name);
|
||||
}
|
||||
|
||||
private has(functionName: string) {
|
||||
return this.functionsByName.has(functionName);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import { NoEmptyLines } from '@/application/Parser/Script/Validation/Rules/NoEmp
|
||||
import { ICodeValidator } from '@/application/Parser/Script/Validation/ICodeValidator';
|
||||
import { IScriptCompiler } from './IScriptCompiler';
|
||||
import { ISharedFunctionCollection } from './Function/ISharedFunctionCollection';
|
||||
import { IFunctionCallCompiler } from './Function/Call/Compiler/IFunctionCallCompiler';
|
||||
import { FunctionCallSequenceCompiler } from './Function/Call/Compiler/FunctionCallSequenceCompiler';
|
||||
import { FunctionCallCompiler } from './Function/Call/Compiler/FunctionCallCompiler';
|
||||
import { ISharedFunctionsParser } from './Function/ISharedFunctionsParser';
|
||||
import { SharedFunctionsParser } from './Function/SharedFunctionsParser';
|
||||
import { parseFunctionCalls } from './Function/Call/FunctionCallParser';
|
||||
import { ICompiledCode } from './Function/Call/Compiler/ICompiledCode';
|
||||
import { CompiledCode } from './Function/Call/Compiler/CompiledCode';
|
||||
|
||||
export class ScriptCompiler implements IScriptCompiler {
|
||||
private readonly functions: ISharedFunctionCollection;
|
||||
@@ -21,7 +21,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
||||
functions: readonly FunctionData[] | undefined,
|
||||
syntax: ILanguageSyntax,
|
||||
sharedFunctionsParser: ISharedFunctionsParser = SharedFunctionsParser.instance,
|
||||
private readonly callCompiler: IFunctionCallCompiler = FunctionCallCompiler.instance,
|
||||
private readonly callCompiler: FunctionCallCompiler = FunctionCallSequenceCompiler.instance,
|
||||
private readonly codeValidator: ICodeValidator = CodeValidator.instance,
|
||||
) {
|
||||
if (!syntax) { throw new Error('missing syntax'); }
|
||||
@@ -40,7 +40,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
||||
if (!script) { throw new Error('missing script'); }
|
||||
try {
|
||||
const calls = parseFunctionCalls(script.call);
|
||||
const compiledCode = this.callCompiler.compileCall(calls, this.functions);
|
||||
const compiledCode = this.callCompiler.compileFunctionCalls(calls, this.functions);
|
||||
validateCompiledCode(compiledCode, this.codeValidator);
|
||||
return new ScriptCode(
|
||||
compiledCode.code,
|
||||
@@ -52,7 +52,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
||||
}
|
||||
}
|
||||
|
||||
function validateCompiledCode(compiledCode: ICompiledCode, validator: ICodeValidator): void {
|
||||
function validateCompiledCode(compiledCode: CompiledCode, validator: ICodeValidator): void {
|
||||
[compiledCode.code, compiledCode.revertCode].forEach(
|
||||
(code) => validator.throwIfInvalid(code, [new NoEmptyLines()]),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Structure documented in "docs/collection-files.md"
|
||||
# Structure is documented in "docs/collection-files.md"
|
||||
os: macos
|
||||
scripting:
|
||||
language: shellscript
|
||||
@@ -21,7 +21,7 @@ actions:
|
||||
-
|
||||
category: Privacy cleanup
|
||||
children:
|
||||
-
|
||||
-
|
||||
category: Clear terminal history
|
||||
children:
|
||||
-
|
||||
@@ -48,18 +48,18 @@ actions:
|
||||
# on main HDD
|
||||
sudo rm -rfv ~/.Trash/* &>/dev/null
|
||||
-
|
||||
name: Clear system cache files
|
||||
name: Clear system cache
|
||||
recommend: strict
|
||||
code: |-
|
||||
sudo rm -rfv /Library/Caches/* &>/dev/null
|
||||
sudo rm -rfv /System/Library/Caches/* &>/dev/null
|
||||
sudo rm -rfv ~/Library/Caches/* &>/dev/null
|
||||
-
|
||||
category: Clear OS logs
|
||||
category: Clear operating system logs
|
||||
recommend: strict
|
||||
children:
|
||||
-
|
||||
category: Clear unified logs (diagnostics)
|
||||
category: Clear unified diagnostic logs
|
||||
docs: https://developer.apple.com/documentation/os/logging
|
||||
children:
|
||||
-
|
||||
@@ -69,15 +69,15 @@ actions:
|
||||
sudo rm -rfv /private/var/db/diagnostics/*
|
||||
sudo rm -rfv /var/db/diagnostics/*
|
||||
-
|
||||
name: Clear shared-cache strings data
|
||||
docs:
|
||||
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/)
|
||||
category: Clear system logs
|
||||
children:
|
||||
-
|
||||
name: Clear Apple System Logs (ASL)
|
||||
@@ -94,7 +94,7 @@ actions:
|
||||
docs: https://discussions.apple.com/thread/1829842
|
||||
code: sudo rm -fv /var/log/install.log
|
||||
-
|
||||
name: Clear all system logs
|
||||
name: Clear all system logs in `/var/log/` directory
|
||||
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
|
||||
-
|
||||
@@ -105,7 +105,7 @@ actions:
|
||||
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)
|
||||
name: Clear user activity audit logs (login, logout, authentication, etc.)
|
||||
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
|
||||
@@ -113,7 +113,7 @@ actions:
|
||||
sudo rm -rfv /var/audit/*
|
||||
sudo rm -rfv /private/var/audit/*
|
||||
-
|
||||
name: Clear user logs (user reports)
|
||||
name: Clear user report logs
|
||||
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
|
||||
@@ -134,15 +134,15 @@ actions:
|
||||
category: Clear browser history
|
||||
children:
|
||||
-
|
||||
category: Clear Google Chrome history
|
||||
category: Clear Chrome history
|
||||
children:
|
||||
-
|
||||
name: Clear Google Chrome browsing history
|
||||
name: Clear Chrome browsing history
|
||||
code: |-
|
||||
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History &>/dev/null
|
||||
rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/History-journal &>/dev/null
|
||||
-
|
||||
name: Google Chrome Cache Files
|
||||
name: Clear Chrome cache
|
||||
code: sudo rm -rfv ~/Library/Application\ Support/Google/Chrome/Default/Application\ Cache/* &>/dev/null
|
||||
-
|
||||
category: Clear Safari history
|
||||
@@ -165,7 +165,7 @@ actions:
|
||||
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||
code: rm -f ~/Library/Safari/Downloads.plist
|
||||
-
|
||||
name: Clear Safari top sites
|
||||
name: Clear Safari frequently visited sites
|
||||
docs: https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||
code: rm -f ~/Library/Safari/TopSites.plist
|
||||
-
|
||||
@@ -182,7 +182,7 @@ actions:
|
||||
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
|
||||
name: Clear Safari URL bar web page icons
|
||||
docs:
|
||||
- https://davidkoepi.wordpress.com/2013/04/20/safariforensic/
|
||||
- https://lifehacker.com/safaris-private-browsing-mode-saves-urls-in-an-easily-a-1691944343
|
||||
@@ -194,11 +194,11 @@ actions:
|
||||
- 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 Safari history copy
|
||||
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 term history embedded in Safari preferences
|
||||
docs: https://krypted.com/tag/recentsearchstrings/
|
||||
code: defaults write ~/Library/Preferences/com.apple.Safari RecentSearchStrings '( )'
|
||||
-
|
||||
@@ -215,11 +215,11 @@ actions:
|
||||
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 allowed URLs for Safari notifications
|
||||
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||
code: rm -f ~/Library/Safari/UserNotificationPreferences.plist
|
||||
-
|
||||
name: Clear Safari per-site preferences for Downloads, Geolocation, PopUps, and Autoplays
|
||||
name: Clear Safari preferences for downloads, geolocation, pop-ups, and autoplay per site
|
||||
docs: https://blog.d204n6.com/2020/09/macos-safari-preferences-and-privacy.html
|
||||
code: rm -f ~/Library/Safari/PerSitePreferences.db
|
||||
-
|
||||
@@ -231,15 +231,15 @@ actions:
|
||||
sudo rm -rf ~/Library/Caches/Mozilla/
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/netpredictions.sqlite
|
||||
-
|
||||
name: Delete Firefox form history
|
||||
name: Clear Firefox form history
|
||||
code: |-
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.sqlite
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/formhistory.dat
|
||||
-
|
||||
name: Delete Firefox site preferences
|
||||
name: Clear Firefox site preferences
|
||||
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/content-prefs.sqlite
|
||||
-
|
||||
name: Delete Firefox session restore data (loads after the browser closes or crashes)
|
||||
name: Clear Firefox session restore data (loads after the browser closes or crashes)
|
||||
code: |-
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionCheckpoints.json
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore*.js*
|
||||
@@ -250,7 +250,7 @@ actions:
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/previous.bak*
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/sessionstore-backups/upgrade.js*-20*
|
||||
-
|
||||
name: Delete Firefox passwords
|
||||
name: Clear Firefox passwords
|
||||
docs: https://web.archive.org/web/20210425202923/http://kb.mozillazine.org/Password_Manager
|
||||
code: |-
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.txt
|
||||
@@ -259,20 +259,20 @@ actions:
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/signons.sqlite
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/logins.json
|
||||
-
|
||||
name: Delete Firefox HTML5 cookies
|
||||
name: Clear Firefox HTML5 cookies
|
||||
code: rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/webappsstore.sqlite
|
||||
-
|
||||
name: Delete Firefox crash reports
|
||||
name: Clear Firefox crash reports
|
||||
code: |-
|
||||
rm -rfv ~/Library/Application\ Support/Firefox/Crash\ Reports/
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/minidumps/*.dmp
|
||||
-
|
||||
name: Delete Firefox backup files
|
||||
name: Clear Firefox backup files
|
||||
code: |-
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.json
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/bookmarkbackups/*.jsonlz4
|
||||
-
|
||||
name: Delete Firefox cookies
|
||||
name: Clear Firefox cookies
|
||||
code: |-
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.txt
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite
|
||||
@@ -280,7 +280,7 @@ actions:
|
||||
rm -fv ~/Library/Application\ Support/Firefox/Profiles/*/cookies.sqlite-wal
|
||||
rm -rfv ~/Library/Application\ Support/Firefox/Profiles/*/storage/default/http*
|
||||
-
|
||||
category: Clear third party application data
|
||||
category: Clear third-party application data
|
||||
children:
|
||||
-
|
||||
name: Clear Adobe cache
|
||||
@@ -290,18 +290,18 @@ actions:
|
||||
name: Clear Gradle cache
|
||||
recommend: strict
|
||||
code: |-
|
||||
if [ -d "/Users/${HOST}/.gradle/caches" ]; then
|
||||
if [ -d "~/.gradle/caches" ]; then
|
||||
rm -rfv ~/.gradle/caches/ &> /dev/null
|
||||
fi
|
||||
-
|
||||
name: Clear Dropbox cache
|
||||
recommend: standard
|
||||
code: |-
|
||||
if [ -d "/Users/${HOST}/Dropbox" ]; then
|
||||
if [ -d "~/Dropbox/.dropbox.cache" ]; then
|
||||
sudo rm -rfv ~/Dropbox/.dropbox.cache/* &>/dev/null
|
||||
fi
|
||||
-
|
||||
name: Clear Google Drive file stream cache
|
||||
name: Clear Google Drive File Stream cache
|
||||
recommend: standard
|
||||
code: |-
|
||||
killall "Google Drive File Stream"
|
||||
@@ -323,21 +323,54 @@ actions:
|
||||
brew tap --repair &>/dev/null
|
||||
fi
|
||||
-
|
||||
name: Clear any old versions of Ruby gems
|
||||
name: Clear old Ruby gem versions
|
||||
recommend: strict
|
||||
code: |-
|
||||
if type "gem" &> /dev/null; then
|
||||
gem cleanup &>/dev/null
|
||||
fi
|
||||
-
|
||||
name: Clear Docker
|
||||
name: Clear unused Docker data
|
||||
recommend: strict
|
||||
docs: |-
|
||||
This script frees up disk space, but also improves user privacy by:
|
||||
|
||||
1. **Removal of stopped containers**: Containers often run applications or services that might process sensitive
|
||||
or personal data. Even if a container is stopped, its filesystem remains intact, and potentially sensitive data inside
|
||||
it can be accessed. By removing stopped containers, we eliminate this potential privacy risk.
|
||||
|
||||
2. **Deletion of unused images**: Images can sometimes contain sensitive information, especially if they were built
|
||||
from `Dockerfile`s that copied local files or were used in scenarios where sensitive data was processed. Deleting unused
|
||||
images ensures that any inadvertent sensitive information embedded in those images is eradicated.
|
||||
|
||||
3. **Cleanup of network configurations**: Networks, especially custom ones, can contain configurations that reveal details
|
||||
about system architecture, inter-container communication, or even hardcoded secrets. Removing unused networks mitigates
|
||||
risks associated with lingering, outdated, or insecure configurations.
|
||||
|
||||
4. **Elimination of build cache**: The Docker build process uses a cache to speed up image creation. This cache can contain
|
||||
remnants of previous builds, including potentially sensitive data or files. Pruning the build cache ensures that these remnants
|
||||
are deleted, further safeguarding privacy.
|
||||
|
||||
5. **Footprint reduction**: By consistently pruning unused Docker objects, the overall footprint of Docker on the system is
|
||||
reduced. This makes it harder for malicious actors to exploit any lingering or overlooked vulnerabilities in the system or Docker
|
||||
itself.
|
||||
|
||||
This script runs `docker system prune -af` command to clean up unused Docker data [1].
|
||||
|
||||
Specifically, the command will [1]:
|
||||
|
||||
- Remove all stopped containers.
|
||||
- Remove all networks not used by at least one container.
|
||||
- Remove all images not used by any container.
|
||||
- Remove all build cache.
|
||||
|
||||
[1]: https://web.archive.org/web/20230810171526/https://docs.docker.com/engine/reference/commandline/system_prune/ "docker system prune | Docker Documentation"
|
||||
code: |-
|
||||
if type "docker" &> /dev/null; then
|
||||
docker system prune -af
|
||||
fi
|
||||
-
|
||||
name: Clear Pyenv-VirtualEnv cache
|
||||
name: Clear Pyenv-Virtualenv cache
|
||||
recommend: strict
|
||||
code: |-
|
||||
if [ "$PYENV_VIRTUALENV_CACHE_PATH" ]; then
|
||||
@@ -359,22 +392,22 @@ actions:
|
||||
yarn cache clean --force
|
||||
fi
|
||||
-
|
||||
category: iOS Cleanup
|
||||
category: Clear iOS usage data
|
||||
children:
|
||||
-
|
||||
name: Clear iOS applications
|
||||
name: Clear iOS app copies from iTunes
|
||||
recommend: strict
|
||||
code: rm -rfv ~/Music/iTunes/iTunes\ Media/Mobile\ Applications/* &>/dev/null
|
||||
-
|
||||
name: Clear iOS photo caches
|
||||
name: Clear iOS photo cache
|
||||
recommend: standard
|
||||
code: rm -rf ~/Pictures/iPhoto\ Library/iPod\ Photo\ Cache/*
|
||||
-
|
||||
name: Remove iOS Device Backups
|
||||
name: Clear iOS Device Backups
|
||||
recommend: strict
|
||||
code: rm -rfv ~/Library/Application\ Support/MobileSync/Backup/* &>/dev/null
|
||||
-
|
||||
name: Clear iOS Simulators
|
||||
name: Clear iOS simulators
|
||||
recommend: strict
|
||||
code: |-
|
||||
if type "xcrun" &>/dev/null; then
|
||||
@@ -385,7 +418,7 @@ actions:
|
||||
xcrun simctl erase all
|
||||
fi
|
||||
-
|
||||
name: Clear the list of iOS devices connected
|
||||
name: Clear list of connected iOS devices
|
||||
recommend: strict
|
||||
code: |-
|
||||
sudo defaults delete /Users/$USER/Library/Preferences/com.apple.iPod.plist "conn:128:Last Connect"
|
||||
@@ -394,7 +427,7 @@ actions:
|
||||
sudo defaults delete /Library/Preferences/com.apple.iPod.plist Devices
|
||||
sudo rm -rfv /var/db/lockdown/*
|
||||
-
|
||||
name: Clear XCode Derived Data and Archives
|
||||
name: Clear Xcode's derived data and archives
|
||||
recommend: strict
|
||||
code: |-
|
||||
rm -rfv ~/Library/Developer/Xcode/DerivedData/* &>/dev/null
|
||||
@@ -407,51 +440,51 @@ actions:
|
||||
sudo dscacheutil -flushcache
|
||||
sudo killall -HUP mDNSResponder
|
||||
-
|
||||
name: Purge inactive memory
|
||||
name: Clear inactive memory
|
||||
recommend: standard
|
||||
code: sudo purge
|
||||
-
|
||||
category: Reset privacy permissions for all applications
|
||||
category: Clear all privacy permissions for applications
|
||||
children:
|
||||
-
|
||||
name: Reset camera permissions
|
||||
name: Clear "camera" permissions
|
||||
code: tccutil reset Camera
|
||||
-
|
||||
name: Reset microphone permissions
|
||||
name: Clear "microphone" permissions
|
||||
code: tccutil reset Microphone
|
||||
-
|
||||
name: Reset accessibility permissions
|
||||
name: Clear "accessibility" permissions
|
||||
code: tccutil reset Accessibility
|
||||
-
|
||||
name: Reset screen capture permissions
|
||||
name: Clear "screen capture" permissions
|
||||
code: tccutil reset ScreenCapture
|
||||
-
|
||||
name: Reset reminders permissions
|
||||
name: Clear "reminders" permissions
|
||||
code: tccutil reset Reminders
|
||||
-
|
||||
name: Reset photos permissions
|
||||
name: Clear "photos" permissions
|
||||
code: tccutil reset Photos
|
||||
-
|
||||
name: Reset calendar permissions
|
||||
name: Clear "calendar" permissions
|
||||
code: tccutil reset Calendar
|
||||
-
|
||||
name: Reset full disk access permissions
|
||||
name: Clear "full disk access" permissions
|
||||
code: tccutil reset SystemPolicyAllFiles
|
||||
-
|
||||
name: Reset contacts permissions
|
||||
name: Clear "contacts" permissions
|
||||
code: tccutil reset SystemPolicyAllFiles
|
||||
-
|
||||
name: Reset desktop folder permissions
|
||||
name: Clear "desktop folder" permissions
|
||||
code: tccutil reset SystemPolicyDesktopFolder
|
||||
-
|
||||
name: Reset documents folder permissions
|
||||
name: Clear "documents folder" permissions
|
||||
code: tccutil reset SystemPolicyDocumentsFolder
|
||||
-
|
||||
name: Reset downloads permissions
|
||||
name: Clear "downloads" permissions
|
||||
code: tccutil reset SystemPolicyDownloadsFolder
|
||||
-
|
||||
name: Reset all app permissions
|
||||
code: tccutil reset All
|
||||
name: Clear all app permissions
|
||||
code: tccutil reset All
|
||||
-
|
||||
category: Configure programs
|
||||
children:
|
||||
@@ -468,20 +501,20 @@ actions:
|
||||
sudo defaults delete /Library/Preferences/org.mozilla.firefox EnterprisePoliciesEnabled
|
||||
sudo defaults delete /Library/Preferences/org.mozilla.firefox DisableTelemetry
|
||||
-
|
||||
name: Disable Microsoft Office diagnostics data sending
|
||||
name: Disable Microsoft Office telemetry
|
||||
recommend: standard
|
||||
code: defaults write com.microsoft.office DiagnosticDataTypePreference -string ZeroDiagnosticData
|
||||
revertCode: defaults delete com.microsoft.office DiagnosticDataTypePreference
|
||||
-
|
||||
name: Uninstall Google update
|
||||
name: Remove Google Software Update service
|
||||
recommend: strict
|
||||
code: |-
|
||||
googleUpdateFile=~/Library/Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/Contents/Resources/ksinstall
|
||||
if [ -f "$googleUpdateFile" ]; then
|
||||
$googleUpdateFile --nuke
|
||||
echo Uninstalled google update
|
||||
echo 'Uninstalled Google update'
|
||||
else
|
||||
echo Google update file does not exist
|
||||
echo 'Google update file does not exist'
|
||||
fi
|
||||
-
|
||||
name: Disable Homebrew user behavior analytics
|
||||
@@ -514,12 +547,12 @@ actions:
|
||||
docs: |-
|
||||
Parallels Desktop for Mac is software providing hardware virtualization for macOS [1].
|
||||
|
||||
When you use it, it collects and share your personal data to third parties [2]. Personal
|
||||
When you use it, it collects and shares your personal data to third parties [2]. Personal
|
||||
data include IP address of your device, your broad geographical location (country, state
|
||||
(if applicable), and city) and used product [2].
|
||||
|
||||
It includes third-party ads [3] and automatic check for updates [4] by default. Both of these
|
||||
behaviors communicate with online services that reveal data about you.
|
||||
|
||||
It includes third-party advertisements [3] and automatic check for updates [4] by default.
|
||||
Both of these behaviors communicate with online services that reveal personal data about you.
|
||||
|
||||
[1]: https://web.archive.org/web/20221012155943/https://en.wikipedia.org/wiki/Parallels_Desktop_for_Mac "Parallels Desktop for Mac - Wikipedia | en.wikipedia.org"
|
||||
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
|
||||
@@ -527,7 +560,7 @@ actions:
|
||||
[4]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
|
||||
children:
|
||||
-
|
||||
name: Turn off ads in Parallels Desktop
|
||||
name: Disable Parallels Desktop advertisements
|
||||
recommend: standard
|
||||
docs: |-
|
||||
Parallels Desktop in-product notifications to show ads from Parallels or other third
|
||||
@@ -544,7 +577,7 @@ actions:
|
||||
default). It's undocumented but still kept disabled by this script.
|
||||
|
||||
[1]: https://web.archive.org/save/https://forum.parallels.com/threads/unable-to-process-the-upgrade-request.345603/ "Unable to process the upgrade request | Parallels Forums | forum.parallels.com"
|
||||
[2]: https://web.archive.org/web/20221012151800/https://kb.parallels.com/114422 "How do I turn off notifications in Parallels Desktop and Parallels Access? | Knowledge Base | parallels.com"
|
||||
[2]: https://web.archive.org/web/20221012151800/https://kb.parallels.com/114422 "How do I turn off notifications in Parallels Desktop and Parallels Access? | Knowledge Base | parallels.com"
|
||||
code: |-
|
||||
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool yes
|
||||
defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes
|
||||
@@ -552,16 +585,16 @@ actions:
|
||||
defaults write 'com.parallels.Parallels Desktop' 'ProductPromo.ForcePromoOff' -bool no
|
||||
defaults write 'com.parallels.Parallels Desktop' 'WelcomeScreenPromo.PromoOff' -bool yes
|
||||
-
|
||||
category: Disable Parallels Desktop auto-updates
|
||||
category: Disable Parallels Desktop automatic updates
|
||||
docs: |-
|
||||
Parallels Desktop by default checks for updates frequently and automatically downloads them [1].
|
||||
This reveal personal data about [2] you without your control.
|
||||
This reveal personal data about you [2] without your control.
|
||||
|
||||
[1]: https://web.archive.org/web/20221012151953/http://download.parallels.com/stm/docs/en/Parallels_Desktop_Users_Guide/22220.htm "Automatic Updating | Parallels Desktop Users Guide | download.parallels.com"
|
||||
[2]: https://web.archive.org/web/20221012155829/https://www.parallels.com/about/legal/privacy/ "Privacy Statement | parallels.com"
|
||||
children:
|
||||
-
|
||||
name: Disable automatically downloading Parallels Desktop updates
|
||||
name: Disable automatic downloads for Parallels Desktop updates
|
||||
docs: |-
|
||||
Automatic downloads are enabled by default, and this script disables automatic downloads.
|
||||
|
||||
@@ -570,11 +603,11 @@ actions:
|
||||
- Check: `defaults read 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically'`
|
||||
- Values: 0 - Disabled, 1 - Enabled (default)
|
||||
|
||||
[1]: https://web.archive.org/web/20221012153810/https://download.parallels.com/desktop/v18/docs/en_US/Parallels-Desktop-Business-Edition-Administrators-Guide/37744.htm "Parallels Desktop Business Edition Administrator's Guide v18 - Configuring individual Macs | download.parallels.com"
|
||||
[1]: https://web.archive.org/web/20221012153810/https://download.parallels.com/desktop/v18/docs/en_US/Parallels-Desktop-Business-Edition-Administrators-Guide/37744.htm "Parallels Desktop Business Edition Administrator's Guide v18 - Configuring individual Macs | download.parallels.com"
|
||||
code: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool no
|
||||
revertCode: defaults write 'com.parallels.Parallels Desktop' 'Application preferences.Download updates automatically' -bool yes
|
||||
-
|
||||
name: Disable automatically checking for Parallels Desktop updates
|
||||
name: Disable automatic checks for Parallels Desktop updates
|
||||
docs: |-
|
||||
Automatic checks are weekly by default, and this script disables the checks completely.
|
||||
|
||||
@@ -593,7 +626,7 @@ actions:
|
||||
category: Configure Apple Remote Desktop
|
||||
children:
|
||||
-
|
||||
name: Deactivate the Remote Management Service
|
||||
name: Disable remote management service
|
||||
recommend: strict
|
||||
code: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -deactivate -stop
|
||||
revertCode: sudo /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -activate -restart -agent -console
|
||||
@@ -604,26 +637,26 @@ actions:
|
||||
sudo rm -rf /var/db/RemoteManagement
|
||||
sudo defaults delete /Library/Preferences/com.apple.RemoteDesktop.plist
|
||||
defaults delete ~/Library/Preferences/com.apple.RemoteDesktop.plist
|
||||
sudo rm -r /Library/Application\ Support/Apple/Remote\ Desktop/
|
||||
sudo rm -rf /Library/Application\ Support/Apple/Remote\ Desktop/
|
||||
rm -r ~/Library/Application\ Support/Remote\ Desktop/
|
||||
rm -r ~/Library/Containers/com.apple.RemoteDesktop
|
||||
-
|
||||
name: Disable Internet based spell correction
|
||||
name: Disable online spell correction
|
||||
code: defaults write NSGlobalDomain WebAutomaticSpellingCorrectionEnabled -bool false
|
||||
revertCode: defaults delete NSGlobalDomain WebAutomaticSpellingCorrectionEnabled
|
||||
-
|
||||
name: Disable Remote Apple Events
|
||||
name: Disable remote Apple events
|
||||
recommend: strict
|
||||
code: sudo systemsetup -setremoteappleevents off
|
||||
revertCode: sudo systemsetup -setremoteappleevents on
|
||||
-
|
||||
name: Do not store documents to iCloud Drive by default
|
||||
name: Disable automatic storage of documents in iCloud Drive
|
||||
docs: https://macos-defaults.com/finder/nsdocumentsavenewdocumentstocloud.html
|
||||
recommend: standard
|
||||
code: defaults write NSGlobalDomain NSDocumentSaveNewDocumentsToCloud -bool false
|
||||
revertCode: defaults delete NSGlobalDomain NSDocumentSaveNewDocumentsToCloud
|
||||
-
|
||||
name: Do not show recent items on dock
|
||||
name: Disable display of recent applications on Dock
|
||||
docs: https://developer.apple.com/documentation/devicemanagement/dock
|
||||
code: defaults write com.apple.dock show-recents -bool false
|
||||
revertCode: defaults delete com.apple.dock show-recents
|
||||
@@ -636,7 +669,7 @@ actions:
|
||||
category: Configure Siri
|
||||
children:
|
||||
-
|
||||
name: Opt-out from Siri data collection
|
||||
name: Disable participation in 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'
|
||||
@@ -683,7 +716,7 @@ actions:
|
||||
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''
|
||||
>&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
|
||||
@@ -694,15 +727,15 @@ actions:
|
||||
code: defaults write com.apple.SetupAssistant 'DidSeeSiriSetup' -bool True
|
||||
revertCode: defaults delete com.apple.SetupAssistant 'DidSeeSiriSetup'
|
||||
-
|
||||
category: Hide Siri
|
||||
category: Remove Siri from user interface
|
||||
children:
|
||||
-
|
||||
name: Hide Siri from menu bar
|
||||
name: Remove 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
|
||||
name: Remove Siri from status menu
|
||||
recommend: strict
|
||||
docs: https://derflounder.wordpress.com/2016/09/20/blocking-siri-on-macos-sierra/
|
||||
code: |-
|
||||
@@ -712,11 +745,11 @@ actions:
|
||||
defaults delete com.apple.Siri 'StatusMenuVisible'
|
||||
defaults delete com.apple.Siri 'UserHasDeclinedEnable'
|
||||
-
|
||||
name: Disable Spotlight indexing
|
||||
name: Disable Spotlight indexing
|
||||
code: sudo mdutil -i off -d /
|
||||
revertCode: sudo mdutil -i on /
|
||||
-
|
||||
name: Disable Personalized advertisements and identifier collection
|
||||
name: Disable personalized advertisements and identifier tracking
|
||||
recommend: standard
|
||||
docs: |-
|
||||
This script enhances your privacy by deactivating Personalized Ads and disabling the collection
|
||||
@@ -746,7 +779,7 @@ actions:
|
||||
|
||||
Please note: The `forceLimitAdTracking` key limits ad tracking [3] [4] and is found in CIS
|
||||
benchmarks for macOS [4]. However, the official macOS documentation specifies that it is
|
||||
applicable only to iOS 7 and later versions, not to macOS [3]. The key does not exist on the OS
|
||||
applicable only to iOS 7 and newer versions, not to macOS [3]. The key does not exist on the OS
|
||||
by default.
|
||||
|
||||
[1]: https://web.archive.org/web/20230731152633/https://www.apple.com/legal/privacy/data/en/apple-advertising/ "Legal - Apple Advertising & Privacy - Apple"
|
||||
@@ -789,7 +822,7 @@ actions:
|
||||
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
|
||||
name: Enable firewall logging
|
||||
recommend: standard
|
||||
docs:
|
||||
- https://www.stigviewer.com/stig/apple_os_x_10.13/2018-10-01/finding/V-81671
|
||||
@@ -801,7 +834,7 @@ actions:
|
||||
/usr/libexec/ApplicationFirewall/socketfilterfw --setloggingmode off
|
||||
sudo defaults write /Library/Preferences/com.apple.alf loggingenabled -bool false
|
||||
-
|
||||
name: Turn on stealth mode
|
||||
name: Enable stealth mode
|
||||
recommend: standard
|
||||
docs:
|
||||
- https://www.stigviewer.com/stig/apple_os_x_10.8_mountain_lion_workstation/2015-02-10/finding/V-51327
|
||||
@@ -816,16 +849,16 @@ actions:
|
||||
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
|
||||
category: Disable automatic permission for incoming traffic in applications
|
||||
children:
|
||||
-
|
||||
name: Prevent automatically allowing incoming connections to signed apps
|
||||
name: Disable automatic incoming connections for 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
|
||||
name: Disable automatic incoming connections for 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
|
||||
@@ -845,18 +878,18 @@ actions:
|
||||
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
|
||||
category: Enable protective screen saver
|
||||
children:
|
||||
-
|
||||
name: Require a password to wake the computer from sleep or screen saver
|
||||
name: Enable password requirement for waking 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
|
||||
name: Enable session lock five seconds after screen saver initiation
|
||||
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.
|
||||
# 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'
|
||||
-
|
||||
@@ -864,36 +897,36 @@ actions:
|
||||
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)
|
||||
name: Disable guest sign-in from login screen
|
||||
code: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool NO
|
||||
revertCode: sudo defaults write /Library/Preferences/com.apple.loginwindow GuestEnabled -bool YES
|
||||
-
|
||||
name: Disable guest access to file shares over AF
|
||||
code: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool NO
|
||||
revertCode: sudo defaults write /Library/Preferences/SystemConfiguration/com.apple.smb.server AllowGuestAccess -bool YES
|
||||
-
|
||||
name: Disable guest access to file shares over SMB
|
||||
code: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool NO
|
||||
revertCode: sudo defaults write /Library/Preferences/com.apple.AppleFileServer guestAccess -bool YES
|
||||
-
|
||||
category: Disable unauthorized connections
|
||||
children:
|
||||
-
|
||||
name: Disable incoming SSH and SFTP remote logins
|
||||
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
|
||||
name: Disable the 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.
|
||||
# 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'
|
||||
@@ -921,13 +954,13 @@ actions:
|
||||
- https://www.cups.org/doc/security.html # Security risks
|
||||
children:
|
||||
-
|
||||
name: Disable sharing of local printers with other computers
|
||||
name: Disable local printer sharing 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
|
||||
name: Disable printing from external addresses, including the internet
|
||||
recommend: standard
|
||||
docs: https://www.cups.org/doc/man-cupsctl.html
|
||||
code: cupsctl --no-remote-any
|
||||
@@ -952,7 +985,7 @@ actions:
|
||||
category: Clean File Quarantine from downloaded files
|
||||
children:
|
||||
-
|
||||
name: Clear File Quarantine logs of all downloaded files
|
||||
name: Clear logs of all downloaded files from File Quarantine
|
||||
recommend: strict
|
||||
docs:
|
||||
- https://www.macobserver.com/tips/how-to/your-mac-remembers-everything-you-download-heres-how-to-clear-download-history/
|
||||
@@ -969,7 +1002,7 @@ actions:
|
||||
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
|
||||
has_system_immutable_flag=true
|
||||
fi
|
||||
if ls -lO "$db_file" | grep --silent 'uchg'; then
|
||||
sudo chflags nouchg "$db_file"
|
||||
@@ -978,7 +1011,7 @@ actions:
|
||||
fi
|
||||
sqlite3 "$db_file" "$db_query"
|
||||
echo "Executed the query \"$db_query\""
|
||||
if [ "$has_sytem_immutable_flag" = true ] ; then
|
||||
if [ "$has_system_immutable_flag" = true ] ; then
|
||||
sudo chflags schg "$db_file"
|
||||
echo "Added system immutable flag back"
|
||||
fi
|
||||
@@ -1012,10 +1045,10 @@ actions:
|
||||
' \
|
||||
{} \;
|
||||
-
|
||||
category: Disable File Quarantine from tracking downloaded files
|
||||
category: Disable macOS File Quarantine tracking for downloaded files
|
||||
children:
|
||||
-
|
||||
name: Prevent quarantine from logging downloaded files
|
||||
name: Disable downloaded file logging in quarantine
|
||||
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/
|
||||
@@ -1038,7 +1071,7 @@ actions:
|
||||
>&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)
|
||||
name: Disable extended quarantine attribute for downloaded files (disables warning)
|
||||
# Disables dialogs shown when opening an application for the first time
|
||||
# i.e. "Application Downloaded from Internet" quarantine warning.
|
||||
docs:
|
||||
@@ -1054,7 +1087,7 @@ actions:
|
||||
# Can protect against unknown threats.
|
||||
children:
|
||||
-
|
||||
name: Prevent Gatekeeper from automatically reactivating itself
|
||||
name: Disable Gatekeeper's automatic reactivation
|
||||
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/
|
||||
@@ -1071,8 +1104,8 @@ actions:
|
||||
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 ) \
|
||||
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
|
||||
@@ -1090,8 +1123,8 @@ actions:
|
||||
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 ) \
|
||||
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
|
||||
@@ -1107,7 +1140,7 @@ actions:
|
||||
fi
|
||||
fi
|
||||
-
|
||||
name: Disable Library Validation Entitlement (checks signature of libraries)
|
||||
name: Disable library validation entitlement (library signature validation)
|
||||
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
|
||||
@@ -1121,25 +1154,25 @@ actions:
|
||||
- https://macadminsdoc.readthedocs.io/en/master/Profiles-and-Settings/OS-X-Updates.html
|
||||
children:
|
||||
-
|
||||
name: Disable automatically checking for updates
|
||||
name: Disable automatic checks for updates
|
||||
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||
code: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool false
|
||||
revertCode: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticCheckEnabled' -bool true
|
||||
-
|
||||
name: Disable automatically downloading new updates when available
|
||||
name: Disable automatic downloads for updates
|
||||
docs: https://developer.apple.com/documentation/devicemanagement/softwareupdate
|
||||
code: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool false
|
||||
revertCode: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticDownload' -bool true
|
||||
-
|
||||
name: Disable automatically installing macOS updates
|
||||
name: Disable automatic installation of macOS updates
|
||||
docs:
|
||||
# References for AutoUpdateRestartRequired
|
||||
- https://kb.vmware.com/s/article/2960635
|
||||
@@ -1149,48 +1182,48 @@ actions:
|
||||
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)
|
||||
# For Mojave and newer (>= 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)
|
||||
# For Mojave and newer (>= 10.14)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallMacOSUpdates' -bool true
|
||||
-
|
||||
name: Disable automatically updating app from the App Store
|
||||
name: Disable automatic app updates 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)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool false
|
||||
# For Mojave and later (>= 10.14)
|
||||
# For Mojave and newer (>= 10.14)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool false
|
||||
revertCode: |-
|
||||
# For OS X Yosemite and later
|
||||
# For OS X Yosemite and newer
|
||||
sudo defaults write /Library/Preferences/com.apple.commerce 'AutoUpdate' -bool true
|
||||
# For Mojave and later (>= 10.14)
|
||||
# For Mojave and newer (>= 10.14)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AutomaticallyInstallAppUpdates' -bool true
|
||||
-
|
||||
name: Disable installation of macOS beta releases
|
||||
name: Disable macOS beta release installation
|
||||
docs: https://support.apple.com/en-gb/HT203018
|
||||
code: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool false
|
||||
revertCode: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'AllowPreReleaseInstallation' -bool true
|
||||
-
|
||||
name: Disable automatically installing configuration data (e.g. XProtect, Gatekeeper, MRT)
|
||||
name: Disable automatic installation for 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)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool false
|
||||
revertCode: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'ConfigDataInstall' -bool true
|
||||
-
|
||||
name: Disable automatically installing system data files and security updates
|
||||
name: Disable automatic installation for system data files and security updates
|
||||
docs:
|
||||
# References for CriticalUpdateInstall
|
||||
- https://derflounder.wordpress.com/2014/12/24/managing-os-xs-automatic-security-updates/
|
||||
@@ -1198,10 +1231,10 @@ actions:
|
||||
# References for softwareupdate --background-critical
|
||||
- https://managingosx.wordpress.com/2013/04/30/undocumented-options/
|
||||
code: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 10.10)
|
||||
sudo defaults write /Library/Preferences/com.apple.SoftwareUpdate 'CriticalUpdateInstall' -bool false
|
||||
revertCode: |-
|
||||
# For OS X Yosemite and later (>= 10.10)
|
||||
# For OS X Yosemite and newer (>= 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
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
export class Clipboard {
|
||||
public static copyText(text: string): void {
|
||||
const el = document.createElement('textarea');
|
||||
el.value = text;
|
||||
el.setAttribute('readonly', ''); // to avoid focus
|
||||
el.style.position = 'absolute';
|
||||
el.style.left = '-9999px';
|
||||
document.body.appendChild(el);
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(el);
|
||||
}
|
||||
}
|
||||
1
src/presentation/assets/icons/battery-full.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 160c8.8 0 16 7.2 16 16V336c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16H464zM80 96C35.8 96 0 131.8 0 176V336c0 44.2 35.8 80 80 80H464c44.2 0 80-35.8 80-80V320c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32V176c0-44.2-35.8-80-80-80H80zm368 96H96V320H448V192z"/></svg>
|
||||
|
After Width: | Height: | Size: 392 B |
1
src/presentation/assets/icons/battery-half.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M464 160c8.8 0 16 7.2 16 16V336c0 8.8-7.2 16-16 16H80c-8.8 0-16-7.2-16-16V176c0-8.8 7.2-16 16-16H464zM80 96C35.8 96 0 131.8 0 176V336c0 44.2 35.8 80 80 80H464c44.2 0 80-35.8 80-80V320c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32V176c0-44.2-35.8-80-80-80H80zm208 96H96V320H288V192z"/></svg>
|
||||
|
After Width: | Height: | Size: 392 B |
1
src/presentation/assets/icons/circle-info.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h24V272H216c-13.3 0-24-10.7-24-24s10.7-24 24-24h48c13.3 0 24 10.7 24 24v88h8c13.3 0 24 10.7 24 24s-10.7 24-24 24H216c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 363 B |
1
src/presentation/assets/icons/copy.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M208 0H332.1c12.7 0 24.9 5.1 33.9 14.1l67.9 67.9c9 9 14.1 21.2 14.1 33.9V336c0 26.5-21.5 48-48 48H208c-26.5 0-48-21.5-48-48V48c0-26.5 21.5-48 48-48zM48 128h80v64H64V448H256V416h64v48c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V176c0-26.5 21.5-48 48-48z"/></svg>
|
||||
|
After Width: | Height: | Size: 365 B |
1
src/presentation/assets/icons/desktop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M64 0C28.7 0 0 28.7 0 64V352c0 35.3 28.7 64 64 64H240l-10.7 32H160c-17.7 0-32 14.3-32 32s14.3 32 32 32H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H346.7L336 416H512c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64H64zM512 64V288H64V64H512z"/></svg>
|
||||
|
After Width: | Height: | Size: 342 B |
1
src/presentation/assets/icons/face-smile.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm177.6 62.1C192.8 334.5 218.8 352 256 352s63.2-17.5 78.4-33.9c9-9.7 24.2-10.4 33.9-1.4s10.4 24.2 1.4 33.9c-22 23.8-60 49.4-113.6 49.4s-91.7-25.5-113.6-49.4c-9-9.7-8.4-24.9 1.4-33.9s24.9-8.4 33.9 1.4zM144.4 208a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm192-32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 495 B |
1
src/presentation/assets/icons/file-arrow-down.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M64 0C28.7 0 0 28.7 0 64V448c0 35.3 28.7 64 64 64H320c35.3 0 64-28.7 64-64V160H256c-17.7 0-32-14.3-32-32V0H64zM256 0V128H384L256 0zM216 232V334.1l31-31c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-72 72c-9.4 9.4-24.6 9.4-33.9 0l-72-72c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l31 31V232c0-13.3 10.7-24 24-24s24 10.7 24 24z"/></svg>
|
||||
|
After Width: | Height: | Size: 428 B |
1
src/presentation/assets/icons/floppy-disk.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M64 32C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H384c35.3 0 64-28.7 64-64V173.3c0-17-6.7-33.3-18.7-45.3L352 50.7C340 38.7 323.7 32 306.7 32H64zm0 96c0-17.7 14.3-32 32-32H288c17.7 0 32 14.3 32 32v64c0 17.7-14.3 32-32 32H96c-17.7 0-32-14.3-32-32V128zM224 288a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"/></svg>
|
||||
|
After Width: | Height: | Size: 407 B |
1
src/presentation/assets/icons/folder-open.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path d="M384 480h48c11.4 0 21.9-6 27.6-15.9l112-192c5.8-9.9 5.8-22.1 .1-32.1S555.5 224 544 224H144c-11.4 0-21.9 6-27.6 15.9L48 357.1V96c0-8.8 7.2-16 16-16H181.5c4.2 0 8.3 1.7 11.3 4.7l26.5 26.5c21 21 49.5 32.8 79.2 32.8H416c8.8 0 16 7.2 16 16v32h48V160c0-35.3-28.7-64-64-64H298.5c-17 0-33.3-6.7-45.3-18.7L226.7 50.7c-12-12-28.3-18.7-45.3-18.7H64C28.7 32 0 60.7 0 96V416c0 35.3 28.7 64 64 64H87.7 384z"/></svg>
|
||||
|
After Width: | Height: | Size: 503 B |
1
src/presentation/assets/icons/folder.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 96C0 60.7 28.7 32 64 32H196.1c19.1 0 37.4 7.6 50.9 21.1L289.9 96H448c35.3 0 64 28.7 64 64V416c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V96zM64 80c-8.8 0-16 7.2-16 16V416c0 8.8 7.2 16 16 16H448c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16H286.6c-10.6 0-20.8-4.2-28.3-11.7L213.1 87c-4.5-4.5-10.6-7-17-7H64z"/></svg>
|
||||
|
After Width: | Height: | Size: 419 B |
1
src/presentation/assets/icons/github.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/presentation/assets/icons/globe.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M352 256c0 22.2-1.2 43.6-3.3 64H163.3c-2.2-20.4-3.3-41.8-3.3-64s1.2-43.6 3.3-64H348.7c2.2 20.4 3.3 41.8 3.3 64zm28.8-64H503.9c5.3 20.5 8.1 41.9 8.1 64s-2.8 43.5-8.1 64H380.8c2.1-20.6 3.2-42 3.2-64s-1.1-43.4-3.2-64zm112.6-32H376.7c-10-63.9-29.8-117.4-55.3-151.6c78.3 20.7 142 77.5 171.9 151.6zm-149.1 0H167.7c6.1-36.4 15.5-68.6 27-94.7c10.5-23.6 22.2-40.7 33.5-51.5C239.4 3.2 248.7 0 256 0s16.6 3.2 27.8 13.8c11.3 10.8 23 27.9 33.5 51.5c11.6 26 20.9 58.2 27 94.7zm-209 0H18.6C48.6 85.9 112.2 29.1 190.6 8.4C165.1 42.6 145.3 96.1 135.3 160zM8.1 192H131.2c-2.1 20.6-3.2 42-3.2 64s1.1 43.4 3.2 64H8.1C2.8 299.5 0 278.1 0 256s2.8-43.5 8.1-64zM194.7 446.6c-11.6-26-20.9-58.2-27-94.6H344.3c-6.1 36.4-15.5 68.6-27 94.6c-10.5 23.6-22.2 40.7-33.5 51.5C272.6 508.8 263.3 512 256 512s-16.6-3.2-27.8-13.8c-11.3-10.8-23-27.9-33.5-51.5zM135.3 352c10 63.9 29.8 117.4 55.3 151.6C112.2 482.9 48.6 426.1 18.6 352H135.3zm358.1 0c-30 74.1-93.6 130.9-171.9 151.6c25.5-34.2 45.2-87.7 55.3-151.6H493.4z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
src/presentation/assets/icons/left-right.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M504.3 273.6c4.9-4.5 7.7-10.9 7.7-17.6s-2.8-13-7.7-17.6l-112-104c-7-6.5-17.2-8.2-25.9-4.4s-14.4 12.5-14.4 22l0 56-192 0 0-56c0-9.5-5.7-18.2-14.4-22s-18.9-2.1-25.9 4.4l-112 104C2.8 243 0 249.3 0 256s2.8 13 7.7 17.6l112 104c7 6.5 17.2 8.2 25.9 4.4s14.4-12.5 14.4-22l0-56 192 0 0 56c0 9.5 5.7 18.2 14.4 22s18.9 2.1 25.9-4.4l112-104z"/></svg>
|
||||
|
After Width: | Height: | Size: 440 B |
1
src/presentation/assets/icons/magnifying-glass.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>
|
||||
|
After Width: | Height: | Size: 343 B |
1
src/presentation/assets/icons/play.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z"/></svg>
|
||||
|
After Width: | Height: | Size: 257 B |
1
src/presentation/assets/icons/tag.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176c-12-12-28.3-18.7-45.3-18.7H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
|
||||
|
After Width: | Height: | Size: 310 B |
1
src/presentation/assets/icons/user-secret.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 16c-6.7 0-10.8-2.8-15.5-6.1C201.9 5.4 194 0 176 0c-30.5 0-52 43.7-66 89.4C62.7 98.1 32 112.2 32 128c0 14.3 25 27.1 64.6 35.9c-.4 4-.6 8-.6 12.1c0 17 3.3 33.2 9.3 48H45.4C38 224 32 230 32 237.4c0 1.7 .3 3.4 1 5l38.8 96.9C28.2 371.8 0 423.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7c0-58.5-28.2-110.4-71.7-143L415 242.4c.6-1.6 1-3.3 1-5c0-7.4-6-13.4-13.4-13.4H342.7c6-14.8 9.3-31 9.3-48c0-4.1-.2-8.1-.6-12.1C391 155.1 416 142.3 416 128c0-15.8-30.7-29.9-78-38.6C324 43.7 302.5 0 272 0c-18 0-25.9 5.4-32.5 9.9c-4.8 3.3-8.8 6.1-15.5 6.1zm56 208H267.6c-16.5 0-31.1-10.6-36.3-26.2c-2.3-7-12.2-7-14.5 0c-5.2 15.6-19.9 26.2-36.3 26.2H168c-22.1 0-40-17.9-40-40V169.6c28.2 4.1 61 6.4 96 6.4s67.8-2.3 96-6.4V184c0 22.1-17.9 40-40 40zm-88 96l16 32L176 480 128 288l64 32zm128-32L272 480 240 352l16-32 64-32z"/></svg>
|
||||
|
After Width: | Height: | Size: 934 B |
1
src/presentation/assets/icons/xmark.svg
Normal file
@@ -0,0 +1 @@
|
||||
<!-- Source: Font Awesome 6 --><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"/></svg>
|
||||
|
After Width: | Height: | Size: 390 B |
@@ -35,8 +35,7 @@
|
||||
}
|
||||
|
||||
.#{$name}-leave-active,
|
||||
.#{$name}-enter, // Vue 2.X compatibility
|
||||
.#{$name}-enter-from // Vue 3.X compatibility
|
||||
.#{$name}-enter-from
|
||||
{
|
||||
opacity: 0;
|
||||
|
||||
|
||||
@@ -7,5 +7,3 @@
|
||||
@forward "./mixins";
|
||||
|
||||
@forward "./components/card";
|
||||
|
||||
@forward "./third-party-extensions/tooltip.scss";
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
// Based on https://github.com/Akryum/v-tooltip/blob/83615e394c96ca491a4df04b892ae87e833beb97/demo-src/src/App.vue#L179-L303
|
||||
@use "@/presentation/assets/styles/colors" as *;
|
||||
|
||||
.tooltip {
|
||||
display: block !important;
|
||||
z-index: 10000;
|
||||
.tooltip-inner {
|
||||
background: $color-primary-darkest;
|
||||
color: $color-on-primary;
|
||||
border-radius: 16px;
|
||||
padding: 5px 10px 4px;
|
||||
}
|
||||
.tooltip-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
margin: 5px;
|
||||
border-color: $color-primary-darkest;
|
||||
z-index: 1;
|
||||
}
|
||||
&[x-placement^="top"] {
|
||||
margin-bottom: 5px;
|
||||
.tooltip-arrow {
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
&[aria-hidden='true'] {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity .15s, visibility .15s;
|
||||
}
|
||||
&[aria-hidden='false'] {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
import { IconBootstrapper } from './Modules/IconBootstrapper';
|
||||
import { VueConstructor, IVueBootstrapper } from './IVueBootstrapper';
|
||||
import { VueBootstrapper } from './Modules/VueBootstrapper';
|
||||
import { TooltipBootstrapper } from './Modules/TooltipBootstrapper';
|
||||
import { Bootstrapper } from './Bootstrapper';
|
||||
import { RuntimeSanityValidator } from './Modules/RuntimeSanityValidator';
|
||||
import { AppInitializationLogger } from './Modules/AppInitializationLogger';
|
||||
import { DependencyBootstrapper } from './Modules/DependencyBootstrapper';
|
||||
import type { App } from 'vue';
|
||||
|
||||
export class ApplicationBootstrapper implements IVueBootstrapper {
|
||||
public bootstrap(vue: VueConstructor): void {
|
||||
const bootstrappers = ApplicationBootstrapper.getAllBootstrappers();
|
||||
for (const bootstrapper of bootstrappers) {
|
||||
bootstrapper.bootstrap(vue);
|
||||
export class ApplicationBootstrapper implements Bootstrapper {
|
||||
constructor(private readonly bootstrappers = ApplicationBootstrapper.getAllBootstrappers()) { }
|
||||
|
||||
public async bootstrap(app: App): Promise<void> {
|
||||
for (const bootstrapper of this.bootstrappers) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await bootstrapper.bootstrap(app); // Not running `Promise.all` because order matters.
|
||||
}
|
||||
}
|
||||
|
||||
private static getAllBootstrappers(): IVueBootstrapper[] {
|
||||
private static getAllBootstrappers(): Bootstrapper[] {
|
||||
return [
|
||||
new IconBootstrapper(),
|
||||
new VueBootstrapper(),
|
||||
new TooltipBootstrapper(),
|
||||
new RuntimeSanityValidator(),
|
||||
new DependencyBootstrapper(),
|
||||
new AppInitializationLogger(),
|
||||
];
|
||||
}
|
||||
|
||||
5
src/presentation/bootstrapping/Bootstrapper.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { App } from 'vue';
|
||||
|
||||
export interface Bootstrapper {
|
||||
bootstrap(app: App): Promise<void>;
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import { useAutoUnsubscribedEvents } from '@/presentation/components/Shared/Hook
|
||||
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
||||
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import { useClipboard } from '../components/Shared/Hooks/Clipboard/UseClipboard';
|
||||
import { useCurrentCode } from '../components/Shared/Hooks/UseCurrentCode';
|
||||
|
||||
export function provideDependencies(
|
||||
context: IApplicationContext,
|
||||
@@ -23,6 +25,12 @@ export function provideDependencies(
|
||||
const { events } = api.inject(InjectionKeys.useAutoUnsubscribedEvents)();
|
||||
return useCollectionState(context, events);
|
||||
});
|
||||
registerTransient(InjectionKeys.useClipboard, () => useClipboard());
|
||||
registerTransient(InjectionKeys.useCurrentCode, () => {
|
||||
const { events } = api.inject(InjectionKeys.useAutoUnsubscribedEvents)();
|
||||
const state = api.inject(InjectionKeys.useCollectionState)();
|
||||
return useCurrentCode(state, events);
|
||||
});
|
||||
}
|
||||
|
||||
export interface VueDependencyInjectionApi {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { VueConstructor } from 'vue';
|
||||
|
||||
export interface IVueBootstrapper {
|
||||
bootstrap(vue: VueConstructor): void;
|
||||
}
|
||||
|
||||
export { VueConstructor };
|
||||
@@ -1,13 +1,13 @@
|
||||
import { ILogger } from '@/infrastructure/Log/ILogger';
|
||||
import { IVueBootstrapper } from '../IVueBootstrapper';
|
||||
import { Bootstrapper } from '../Bootstrapper';
|
||||
import { ClientLoggerFactory } from '../ClientLoggerFactory';
|
||||
|
||||
export class AppInitializationLogger implements IVueBootstrapper {
|
||||
export class AppInitializationLogger implements Bootstrapper {
|
||||
constructor(
|
||||
private readonly logger: ILogger = ClientLoggerFactory.Current.logger,
|
||||
) { }
|
||||
|
||||
public bootstrap(): void {
|
||||
public async bootstrap(): Promise<void> {
|
||||
// Do not remove [APP_INIT]; it's a marker used in tests.
|
||||
this.logger.info('[APP_INIT] Application is initialized.');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { inject, type App } from 'vue';
|
||||
import { buildContext } from '@/application/Context/ApplicationContextFactory';
|
||||
import { provideDependencies } from '@/presentation/bootstrapping/DependencyProvider';
|
||||
import { Bootstrapper } from '../Bootstrapper';
|
||||
|
||||
export class DependencyBootstrapper implements Bootstrapper {
|
||||
constructor(
|
||||
private readonly contextFactory = buildContext,
|
||||
private readonly dependencyProvider = provideDependencies,
|
||||
private readonly injector = inject,
|
||||
) { }
|
||||
|
||||
public async bootstrap(app: App): Promise<void> {
|
||||
const context = await this.contextFactory();
|
||||
this.dependencyProvider(context, {
|
||||
provide: app.provide,
|
||||
inject: this.injector,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { library } from '@fortawesome/fontawesome-svg-core';
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons';
|
||||
/** BRAND ICONS (PREFIX: fab) */
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
|
||||
/** REGULAR ICONS (PREFIX: far) */
|
||||
import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons';
|
||||
/** SOLID ICONS (PREFIX: fas (default)) */
|
||||
import {
|
||||
faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop, faTag, faGlobe,
|
||||
faSave, faBatteryFull, faBatteryHalf, faPlay, faArrowsAltH,
|
||||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { IVueBootstrapper, VueConstructor } from '../IVueBootstrapper';
|
||||
|
||||
export class IconBootstrapper implements IVueBootstrapper {
|
||||
public bootstrap(vue: VueConstructor): void {
|
||||
library.add(
|
||||
faGithub,
|
||||
faUserSecret,
|
||||
faSmile,
|
||||
faDesktop,
|
||||
faGlobe,
|
||||
faTag,
|
||||
faFolderOpen,
|
||||
faFolder,
|
||||
faTimes,
|
||||
faFileDownload,
|
||||
faSave,
|
||||
faCopy,
|
||||
faPlay,
|
||||
faSearch,
|
||||
faBatteryFull,
|
||||
faBatteryHalf,
|
||||
faInfoCircle,
|
||||
faArrowsAltH,
|
||||
);
|
||||
vue.component('font-awesome-icon', FontAwesomeIcon);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { validateRuntimeSanity } from '@/infrastructure/RuntimeSanity/SanityChecks';
|
||||
import { IVueBootstrapper } from '../IVueBootstrapper';
|
||||
import { Bootstrapper } from '../Bootstrapper';
|
||||
|
||||
export class RuntimeSanityValidator implements IVueBootstrapper {
|
||||
export class RuntimeSanityValidator implements Bootstrapper {
|
||||
constructor(private readonly validator = validateRuntimeSanity) {
|
||||
|
||||
}
|
||||
|
||||
public bootstrap(): void {
|
||||
public async bootstrap(): Promise<void> {
|
||||
this.validator({
|
||||
validateEnvironmentVariables: true,
|
||||
validateWindowVariables: true,
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import VTooltip from 'v-tooltip';
|
||||
import { VueConstructor, IVueBootstrapper } from '../IVueBootstrapper';
|
||||
|
||||
export class TooltipBootstrapper implements IVueBootstrapper {
|
||||
public bootstrap(vue: VueConstructor): void {
|
||||
vue.use(VTooltip);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { VueConstructor, IVueBootstrapper } from '../IVueBootstrapper';
|
||||
|
||||
export class VueBootstrapper implements IVueBootstrapper {
|
||||
public bootstrap(vue: VueConstructor): void {
|
||||
const { config } = vue;
|
||||
config.productionTip = false;
|
||||
}
|
||||
}
|
||||
@@ -7,20 +7,21 @@
|
||||
<TheCodeButtons class="app__row app__code-buttons" />
|
||||
<TheFooter />
|
||||
</div>
|
||||
<OptionalDevToolkit />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
import TheHeader from '@/presentation/components/TheHeader.vue';
|
||||
import TheFooter from '@/presentation/components/TheFooter/TheFooter.vue';
|
||||
import TheCodeButtons from '@/presentation/components/Code/CodeButtons/TheCodeButtons.vue';
|
||||
import TheScriptArea from '@/presentation/components/Scripts/TheScriptArea.vue';
|
||||
import TheSearchBar from '@/presentation/components/TheSearchBar.vue';
|
||||
import { buildContext } from '@/application/Context/ApplicationContextFactory';
|
||||
import { provideDependencies } from '../bootstrapping/DependencyProvider';
|
||||
|
||||
const singletonAppContext = await buildContext();
|
||||
const OptionalDevToolkit = process.env.NODE_ENV !== 'production'
|
||||
? defineAsyncComponent(() => import('@/presentation/components/DevToolkit/DevToolkit.vue'))
|
||||
: null;
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -29,10 +30,9 @@ export default defineComponent({
|
||||
TheScriptArea,
|
||||
TheSearchBar,
|
||||
TheFooter,
|
||||
OptionalDevToolkit,
|
||||
},
|
||||
setup() {
|
||||
provideDependencies(singletonAppContext); // In Vue 3.0 we can move it to main.ts
|
||||
},
|
||||
setup() { },
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -59,5 +59,4 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<IconButton
|
||||
text="Copy"
|
||||
@click="copyCode"
|
||||
icon-name="copy"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent, inject,
|
||||
} from 'vue';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import IconButton from './IconButton.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IconButton,
|
||||
},
|
||||
setup() {
|
||||
const { copyText } = inject(InjectionKeys.useClipboard)();
|
||||
const { currentCode } = inject(InjectionKeys.useCurrentCode)();
|
||||
|
||||
async function copyCode() {
|
||||
await copyText(currentCode.value);
|
||||
}
|
||||
|
||||
return {
|
||||
copyCode,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<IconButton
|
||||
v-if="canRun"
|
||||
text="Run"
|
||||
@click="executeCode"
|
||||
icon-name="play"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent, computed, inject,
|
||||
} from 'vue';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { CodeRunner } from '@/infrastructure/CodeRunner';
|
||||
import { IReadOnlyApplicationContext } from '@/application/Context/IApplicationContext';
|
||||
import IconButton from './IconButton.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IconButton,
|
||||
},
|
||||
setup() {
|
||||
const { currentState, currentContext } = inject(InjectionKeys.useCollectionState)();
|
||||
const { os, isDesktop } = inject(InjectionKeys.useRuntimeEnvironment);
|
||||
|
||||
const canRun = computed<boolean>(() => getCanRunState(currentState.value.os, isDesktop, os));
|
||||
|
||||
async function executeCode() {
|
||||
await runCode(currentContext);
|
||||
}
|
||||
|
||||
return {
|
||||
isDesktopVersion: isDesktop,
|
||||
canRun,
|
||||
executeCode,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function getCanRunState(
|
||||
selectedOs: OperatingSystem,
|
||||
isDesktopVersion: boolean,
|
||||
hostOs: OperatingSystem,
|
||||
): boolean {
|
||||
const isRunningOnSelectedOs = selectedOs === hostOs;
|
||||
return isDesktopVersion && isRunningOnSelectedOs;
|
||||
}
|
||||
|
||||
async function runCode(context: IReadOnlyApplicationContext) {
|
||||
const runner = new CodeRunner();
|
||||
await runner.runCode(
|
||||
/* code: */ context.state.code.current,
|
||||
/* appName: */ context.app.info.name,
|
||||
/* fileExtension: */ context.state.collection.scripting.fileExtension,
|
||||
);
|
||||
}
|
||||
</script>
|
||||
@@ -1,33 +1,35 @@
|
||||
<template>
|
||||
<button
|
||||
class="button"
|
||||
type="button"
|
||||
@click="onClicked"
|
||||
>
|
||||
<font-awesome-icon
|
||||
class="button__icon"
|
||||
:icon="[iconPrefix, iconName]"
|
||||
size="2x"
|
||||
/>
|
||||
<div class="button__text">{{text}}</div>
|
||||
</button>
|
||||
<div class="button-wrapper">
|
||||
<button
|
||||
class="button"
|
||||
type="button"
|
||||
@click="onClicked"
|
||||
>
|
||||
<AppIcon
|
||||
class="button__icon"
|
||||
:icon="iconName"
|
||||
/>
|
||||
<div class="button__text">{{text}}</div>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, PropType } from 'vue';
|
||||
import { IconName } from '@/presentation/components/Shared/Icon/IconName';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
AppIcon,
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
iconPrefix: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
iconName: {
|
||||
type: String,
|
||||
type: String as PropType<IconName>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
@@ -49,10 +51,20 @@ export default defineComponent({
|
||||
<style scoped lang="scss">
|
||||
@use "@/presentation/assets/styles/main" as *;
|
||||
|
||||
.button-wrapper {
|
||||
position: relative;
|
||||
height: 70px;
|
||||
.button {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background-color: $color-secondary;
|
||||
color: $color-on-secondary;
|
||||
@@ -64,21 +76,23 @@ export default defineComponent({
|
||||
box-shadow: 0 3px 9px $color-primary-darkest;
|
||||
border-radius: 4px;
|
||||
|
||||
&__icon {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
@include clickable;
|
||||
|
||||
width: 10%;
|
||||
min-width: 90px;
|
||||
@include hover-or-touch {
|
||||
background: $color-surface;
|
||||
box-shadow: 0px 2px 10px 5px $color-secondary;
|
||||
.button__text {
|
||||
display: block;
|
||||
}
|
||||
.button__icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@include hover-or-touch('>&__text') {
|
||||
display: block;
|
||||
}
|
||||
@include hover-or-touch('>&__icon') {
|
||||
display: none;
|
||||
}
|
||||
&__text {
|
||||
.button__text {
|
||||
display: none;
|
||||
font-family: $font-artistic;
|
||||
font-size: 1.5em;
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<div>
|
||||
<IconButton
|
||||
:text="isDesktopVersion ? 'Save' : 'Download'"
|
||||
@click="saveCode"
|
||||
:icon-name="isDesktopVersion ? 'floppy-disk' : 'file-arrow-down'"
|
||||
/>
|
||||
<ModalDialog v-if="instructions" v-model="areInstructionsVisible">
|
||||
<InstructionList :data="instructions" />
|
||||
</ModalDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {
|
||||
defineComponent, ref, computed, inject,
|
||||
} from 'vue';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
import { SaveFileDialog, FileType } from '@/infrastructure/SaveFileDialog';
|
||||
import ModalDialog from '@/presentation/components/Shared/Modal/ModalDialog.vue';
|
||||
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import IconButton from '../IconButton.vue';
|
||||
import InstructionList from './Instructions/InstructionList.vue';
|
||||
import { IInstructionListData } from './Instructions/InstructionListData';
|
||||
import { getInstructions, hasInstructions } from './Instructions/InstructionListDataFactory';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
IconButton,
|
||||
InstructionList,
|
||||
ModalDialog,
|
||||
},
|
||||
setup() {
|
||||
const { currentState } = inject(InjectionKeys.useCollectionState)();
|
||||
const { isDesktop } = inject(InjectionKeys.useRuntimeEnvironment);
|
||||
|
||||
const areInstructionsVisible = ref(false);
|
||||
const fileName = computed<string>(() => buildFileName(currentState.value.collection.scripting));
|
||||
const instructions = computed<IInstructionListData | undefined>(() => getDownloadInstructions(
|
||||
currentState.value.collection.os,
|
||||
fileName.value,
|
||||
));
|
||||
|
||||
function saveCode() {
|
||||
saveCodeToDisk(fileName.value, currentState.value);
|
||||
areInstructionsVisible.value = true;
|
||||
}
|
||||
|
||||
return {
|
||||
isDesktopVersion: isDesktop,
|
||||
instructions,
|
||||
fileName,
|
||||
areInstructionsVisible,
|
||||
saveCode,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
function getDownloadInstructions(
|
||||
os: OperatingSystem,
|
||||
fileName: string,
|
||||
): IInstructionListData | undefined {
|
||||
if (!hasInstructions(os)) {
|
||||
return undefined;
|
||||
}
|
||||
return getInstructions(os, fileName);
|
||||
}
|
||||
|
||||
function saveCodeToDisk(fileName: string, state: IReadOnlyCategoryCollectionState) {
|
||||
const content = state.code.current;
|
||||
const type = getType(state.collection.scripting.language);
|
||||
SaveFileDialog.saveFile(content, fileName, type);
|
||||
}
|
||||
|
||||
function getType(language: ScriptingLanguage) {
|
||||
switch (language) {
|
||||
case ScriptingLanguage.batchfile:
|
||||
return FileType.BatchFile;
|
||||
case ScriptingLanguage.shellscript:
|
||||
return FileType.ShellScript;
|
||||
default:
|
||||
throw new Error('unknown file type');
|
||||
}
|
||||
}
|
||||
function buildFileName(scripting: IScriptingDefinition) {
|
||||
const fileName = 'privacy-script';
|
||||
if (scripting.fileExtension) {
|
||||
return `${fileName}.${scripting.fileExtension}`;
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
</script>
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<span class="code-wrapper">
|
||||
<span class="dollar">$</span>
|
||||
<code><slot /></code>
|
||||
<code ref="codeElement"><slot /></code>
|
||||
<TooltipWrapper>
|
||||
<font-awesome-icon
|
||||
<AppIcon
|
||||
class="copy-button"
|
||||
:icon="['fas', 'copy']"
|
||||
icon="copy"
|
||||
@click="copyCode"
|
||||
/>
|
||||
<template v-slot:tooltip>
|
||||
@@ -16,24 +16,36 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, useSlots } from 'vue';
|
||||
import { Clipboard } from '@/infrastructure/Clipboard';
|
||||
import { defineComponent, shallowRef, inject } from 'vue';
|
||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
TooltipWrapper,
|
||||
AppIcon,
|
||||
},
|
||||
setup() {
|
||||
const slots = useSlots();
|
||||
const { copyText } = inject(InjectionKeys.useClipboard)();
|
||||
|
||||
function copyCode() {
|
||||
const code = slots.default()[0].text;
|
||||
Clipboard.copyText(code);
|
||||
const codeElement = shallowRef<HTMLElement | undefined>();
|
||||
|
||||
async function copyCode() {
|
||||
const element = codeElement.value;
|
||||
if (!element) {
|
||||
throw new Error('Code element could not be found.');
|
||||
}
|
||||
const code = element.textContent;
|
||||
if (!code) {
|
||||
throw new Error('Code element does not contain any text.');
|
||||
}
|
||||
await copyText(code);
|
||||
}
|
||||
|
||||
return {
|
||||
copyCode,
|
||||
codeElement,
|
||||
};
|
||||
},
|
||||
});
|
||||